diff --git a/Makefile b/Makefile index 4e4a13f14c6..292802dfacf 100644 --- a/Makefile +++ b/Makefile @@ -893,6 +893,8 @@ DEPRECATION_WARNING_STR := \ WARN_ABOUT_DEPRECATION := $(if $(CONFIG_BOARD_DEPRECATED),echo -e \ -n $(DEPRECATION_WARNING_STR),true) +GENERATED_KERNEL_OBJECT_FILES := + ifeq ($(ARCH),x86) include $(srctree)/arch/x86/Makefile.idt ifeq ($(CONFIG_X86_MMU),y) @@ -907,6 +909,10 @@ ifeq ($(CONFIG_GEN_ISR_TABLES),y) include $(srctree)/arch/common/Makefile.gen_isr_tables endif +ifeq ($(CONFIG_USERSPACE),y) +include $(srctree)/arch/common/Makefile.kobjects +endif + ifneq ($(GENERATED_KERNEL_OBJECT_FILES),) # Identical rule to linker.cmd, but we also define preprocessor LINKER_PASS2. diff --git a/arch/common/Makefile.kobjects b/arch/common/Makefile.kobjects new file mode 100644 index 00000000000..7ba6e7a6cce --- /dev/null +++ b/arch/common/Makefile.kobjects @@ -0,0 +1,57 @@ +GEN_KOBJ_LIST := $(srctree)/scripts/gen_kobject_list.py +PROCESS_GPERF := $(srctree)/scripts/process_gperf.py +OBJ_LIST := kobject_hash.gperf +OUTPUT_SRC_PRE := kobject_hash_preprocessed.c +OUTPUT_SRC := kobject_hash.c +OUTPUT_OBJ := kobject_hash.o +OUTPUT_OBJ_RENAMED := kobject_hash_renamed.o + +SCRIPT_EXTRA_ARGS := + +ifeq ($(KBUILD_VERBOSE),1) +SCRIPT_EXTRA_ARGS += --verbose +endif + +# Scan the kernel binary's DWARF information to produce a table of +# kernel objects which we will pass to gperf +quiet_cmd_gen_kobj_list = KOBJ $@ + cmd_gen_kobj_list = $(GEN_KOBJ_LIST) --kernel $< --output $@ \ + $(SCRIPT_EXTRA_ARGS) + +$(OBJ_LIST): $(PREBUILT_KERNEL) $(GEN_KOBJ_LIST) + $(call cmd,gen_kobj_list) + +# Generate C code which implements a perfect hashtable based on our +# table of kernel objects +quiet_cmd_gperf = GPERF $@ + cmd_gperf = gperf --output-file=$@ $< + +$(OUTPUT_SRC_PRE): $(OBJ_LIST) + $(call cmd,gperf) + +# For our purposes, the code/data generated by gperf is not optimal. +# This script adjusts the generated .c file to greatly reduce the amount +# of code/data generated since we know we are always working with +# pointer values +quiet_cmd_process_gperf = PROCESS $@ + cmd_process_gperf = $(PROCESS_GPERF) -i $< -o $@ $(SCRIPT_EXTRA_ARGS) + +$(OUTPUT_SRC): $(OUTPUT_SRC_PRE) $(PROCESS_GPERF) + $(call cmd,process_gperf) + +# We need precise control of where generated text/data ends up in the final +# kernel image. Disable function/data sections and use objcopy to move +# generated data into special section names +$(OUTPUT_OBJ): KBUILD_CFLAGS += -fno-function-sections -fno-data-sections + +quiet_cmd_kobject_objcopy = OBJCOPY $@ + cmd_kobject_objcopy = $(OBJCOPY) \ + --rename-section .data=.kobject_data.data \ + --rename-section .rodata=.kobject_data.rodata \ + --rename-section .text=.kobject_data.text \ + $< $@ + +$(OUTPUT_OBJ_RENAMED): $(OUTPUT_OBJ) + $(call cmd,kobject_objcopy) + +GENERATED_KERNEL_OBJECT_FILES += $(OUTPUT_OBJ_RENAMED) diff --git a/include/arch/arm/cortex_m/scripts/linker.ld b/include/arch/arm/cortex_m/scripts/linker.ld index bfd6bee72bc..8752e838e17 100644 --- a/include/arch/arm/cortex_m/scripts/linker.ld +++ b/include/arch/arm/cortex_m/scripts/linker.ld @@ -117,6 +117,9 @@ SECTIONS *(.text) *(".text.*") *(.gnu.linkonce.t.*) + +#include + } GROUP_LINK_IN(ROMABLE_REGION) _image_text_end = .; @@ -148,6 +151,9 @@ SECTIONS /* Located in project source directory */ #include #endif + +#include + /* * For XIP images, in order to avoid the situation when __data_rom_start * is 32-bit aligned, but the actual data is placed right after rodata @@ -208,30 +214,6 @@ SECTIONS __app_ram_end = .; #endif /* CONFIG_APPLICATION_MEMORY */ - - SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME,,) - { -#ifndef CONFIG_APPLICATION_MEMORY - _image_ram_start = .; -#endif - __kernel_ram_start = .; - __data_ram_start = .; - KERNEL_INPUT_SECTION(.data) - KERNEL_INPUT_SECTION(".data.*") - -#ifdef CONFIG_CUSTOM_RWDATA_LD -/* Located in project source directory */ -#include -#endif - - } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) - - __data_rom_start = LOADADDR(_DATA_SECTION_NAME); - -#include - - __data_ram_end = .; - SECTION_DATA_PROLOGUE(_BSS_SECTION_NAME,(NOLOAD),) { /* @@ -240,6 +222,11 @@ SECTIONS */ . = ALIGN(4); __bss_start = .; +#ifndef CONFIG_APPLICATION_MEMORY + _image_ram_start = .; +#endif + __kernel_ram_start = .; + KERNEL_INPUT_SECTION(.bss) KERNEL_INPUT_SECTION(".bss.*") KERNEL_INPUT_SECTION(COMMON) @@ -265,6 +252,27 @@ SECTIONS } GROUP_LINK_IN(RAMABLE_REGION) + SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME,,) + { + __data_ram_start = .; + KERNEL_INPUT_SECTION(.data) + KERNEL_INPUT_SECTION(".data.*") + +#ifdef CONFIG_CUSTOM_RWDATA_LD +/* Located in project source directory */ +#include +#endif + + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) + + __data_rom_start = LOADADDR(_DATA_SECTION_NAME); + +#include +#include + + __data_ram_end = .; + + /* Define linker symbols */ _image_ram_end = .; diff --git a/include/arch/x86/linker.ld b/include/arch/x86/linker.ld index 438d4878071..70494a9d644 100644 --- a/include/arch/x86/linker.ld +++ b/include/arch/x86/linker.ld @@ -83,6 +83,9 @@ SECTIONS *(.eini) KEEP(*(.openocd_dbg)) KEEP(*(".openocd_dbg.*")) + +#include + } GROUP_LINK_IN(ROMABLE_REGION) _image_text_end = .; @@ -95,7 +98,6 @@ SECTIONS *(.rodata) *(".rodata.*") *(.gnu.linkonce.r.*) - . = ALIGN(8); _idt_base_address = .; #ifdef LINKER_PASS2 @@ -119,15 +121,20 @@ SECTIONS #include #endif +#include } GROUP_LINK_IN(ROMABLE_REGION) _image_rodata_end = .; - - /* This align directive needs to be after __data_rom_start or XIP - * won't be copying the data from the right LMA - */ MMU_PAGE_ALIGN +#ifdef CONFIG_XIP + /* Kernel ROM extends to the end of flash. Need to do this to program + * the MMU + */ + _image_rom_end = _image_rom_start + KB(CONFIG_ROM_SIZE); +#else + /* ROM ends here, position counter will now be in RAM areas */ _image_rom_end = .; +#endif _image_rom_size = _image_rom_end - _image_rom_start; GROUP_END(ROMABLE_REGION) @@ -170,68 +177,17 @@ SECTIONS __app_ram_size = __app_ram_end - __app_ram_start; #endif /* CONFIG_APPLICATION_MEMORY */ - - SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME, (OPTIONAL),) - { -#ifndef CONFIG_APPLICATION_MEMORY - _image_ram_start = .; -#endif - __kernel_ram_start = .; - __data_ram_start = .; - - KERNEL_INPUT_SECTION(.data) - KERNEL_INPUT_SECTION(".data.*") - *(".kernel.*") - -#ifdef CONFIG_CUSTOM_RWDATA_LD -/* Located in project source directory */ -#include -#endif - -#ifdef CONFIG_GDT_DYNAMIC - KEEP(*(.tss)) - . = ALIGN(8); - _gdt = .; -#ifdef LINKER_PASS2 - KEEP(*(gdt_ram_data)) -#else /* LINKER_PASS2 */ - -#ifdef CONFIG_X86_STACK_PROTECTION -#define GDT_NUM_ENTRIES 5 -#else /* CONFIG_X86_STACK_PROTECTION */ -#define GDT_NUM_ENTRIES 3 -#endif /* CONFIG_X86_STACK_PROTECTION */ - - . += GDT_NUM_ENTRIES * 8; -#endif /* LINKER_PASS2 */ -#endif /* CONFIG_GDT_DYNAMIC */ - -#ifdef CONFIG_X86_MMU - /* Page Tables are located here if MMU is enabled.*/ - MMU_PAGE_ALIGN - __mmu_tables_start = .; - KEEP(*(.mmu_data)); - __mmu_tables_end = .; -#endif - . = ALIGN(4); - } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) - - __data_rom_start = LOADADDR(_DATA_SECTION_NAME); - - -#include - - __data_ram_end = .; - SECTION_PROLOGUE(_BSS_SECTION_NAME, (NOLOAD OPTIONAL),) { /* * For performance, BSS section is forced to be both 4 byte aligned and * a multiple of 4 bytes. */ - . = ALIGN(4); - +#ifndef CONFIG_APPLICATION_MEMORY + _image_ram_start = .; +#endif + __kernel_ram_start = .; __bss_start = .; KERNEL_INPUT_SECTION(.bss) @@ -269,6 +225,67 @@ SECTIONS } GROUP_DATA_LINK_IN(RAMABLE_REGION, RAMABLE_REGION) + SECTION_DATA_PROLOGUE(_DATA_SECTION_NAME, (OPTIONAL),) + { + + __data_ram_start = .; + + KERNEL_INPUT_SECTION(.data) + KERNEL_INPUT_SECTION(".data.*") + *(".kernel.*") + +#ifdef CONFIG_CUSTOM_RWDATA_LD +/* Located in project source directory */ +#include +#endif + +#ifdef CONFIG_GDT_DYNAMIC + KEEP(*(.tss)) + . = ALIGN(8); + _gdt = .; +#ifdef LINKER_PASS2 + KEEP(*(gdt_ram_data)) +#else /* LINKER_PASS2 */ + +#ifdef CONFIG_X86_STACK_PROTECTION +#define GDT_NUM_ENTRIES 5 +#else /* CONFIG_X86_STACK_PROTECTION */ +#define GDT_NUM_ENTRIES 3 +#endif /* CONFIG_X86_STACK_PROTECTION */ + + . += GDT_NUM_ENTRIES * 8; +#endif /* LINKER_PASS2 */ +#endif /* CONFIG_GDT_DYNAMIC */ + + . = ALIGN(4); + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) + + __data_rom_start = LOADADDR(_DATA_SECTION_NAME); + +#include + +#ifdef CONFIG_X86_MMU + /* Can't really predict the size of this section. Anything after this + * should not be affected if addresses change between builds (currently + * just the gperf tables which is fine). + * + * However, __mmu_tables_start *must* remain stable between builds, + * we can't have anything shifting the memory map beforehand. + */ + SECTION_DATA_PROLOGUE(mmu_tables, (OPTIONAL),) + { + /* Page Tables are located here if MMU is enabled.*/ + MMU_PAGE_ALIGN + __mmu_tables_start = .; + KEEP(*(.mmu_data)); + __mmu_tables_end = .; + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#endif + +#include + + __data_ram_end = .; + /* All unused memory also owned by the kernel for heaps */ __kernel_ram_end = PHYS_RAM_ADDR + KB(CONFIG_RAM_SIZE); __kernel_ram_size = __kernel_ram_end - __kernel_ram_start; diff --git a/include/kernel.h b/include/kernel.h index 4023f48bf21..5483c84dd96 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -123,6 +123,103 @@ struct k_timer; struct k_poll_event; struct k_poll_signal; +enum k_objects { + K_OBJ_ALERT, + K_OBJ_DELAYED_WORK, + K_OBJ_MEM_SLAB, + K_OBJ_MSGQ, + K_OBJ_MUTEX, + K_OBJ_PIPE, + K_OBJ_SEM, + K_OBJ_STACK, + K_OBJ_THREAD, + K_OBJ_TIMER, + K_OBJ_WORK, + K_OBJ_WORK_Q, + + K_OBJ_LAST +}; + +#ifdef CONFIG_USERSPACE +/* Table generated by gperf, these objects are retrieved via + * _k_object_find() */ +struct _k_object { + char *name; + char perms[CONFIG_MAX_THREAD_BYTES]; + char type; + char flags; +} __packed; + +#define K_OBJ_FLAG_INITIALIZED BIT(0) +/** + * Ensure a system object is a valid object of the expected type + * + * Searches for the object and ensures that it is indeed an object + * of the expected type, that the caller has the right permissions on it, + * and that the object has been initialized. + * + * This function is intended to be called on the kernel-side system + * call handlers to validate kernel object pointers passed in from + * userspace. + * + * @param obj Address of the kernel object + * @param otype Expected type of the kernel object + * @param init If true, this is for an init function and we will not error + * out if the object is not initialized + * @return 0 If the object is valid + * -EBADF if not a valid object of the specified type + * -EPERM If the caller does not have permissions + * -EINVAL Object is not intitialized + */ +int _k_object_validate(void *obj, enum k_objects otype, int init); + + +/** + * Lookup a kernel object and init its metadata if it exists + * + * Calling this on an object will make it usable from userspace. + * Intended to be called as the last statement in kernel object init + * functions. + * + * @param object Address of the kernel object + */ +void _k_object_init(void *obj); + + +/** + * grant a thread access to a kernel object + * + * The thread will be granted access to the object if the caller is from + * supervisor mode, or the caller is from user mode AND has permissions + * on the object already. + * + * @param object Address of kernel object + * @param thread Thread to grant access to the object + */ +void k_object_grant_access(void *object, struct k_thread *thread); + +#else +static inline int _k_object_validate(void *obj, enum k_objects otype, int init) +{ + ARG_UNUSED(obj); + ARG_UNUSED(otype); + ARG_UNUSED(init); + + return 0; +} + +static inline void _k_object_init(void *obj) +{ + ARG_UNUSED(obj); +} + +static inline void k_object_grant_access(void *object, struct k_thread *thread) +{ + ARG_UNUSED(object); + ARG_UNUSED(thread); +} +#endif /* CONFIG_USERSPACE */ + /* timeouts */ struct _timeout; @@ -196,7 +293,6 @@ struct _thread_base { /* this thread's entry in a timeout queue */ struct _timeout timeout; #endif - }; typedef struct _thread_base _thread_base_t; @@ -2022,6 +2118,7 @@ static inline void k_work_init(struct k_work *work, k_work_handler_t handler) { atomic_clear_bit(work->flags, K_WORK_STATE_PENDING); work->handler = handler; + _k_object_init(work); } /** diff --git a/include/linker/common-ram.ld b/include/linker/common-ram.ld index 5a577af3f6f..31ca5bd27a2 100644 --- a/include/linker/common-ram.ld +++ b/include/linker/common-ram.ld @@ -22,6 +22,17 @@ _static_thread_data_list_end = .; } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#ifdef CONFIG_USERSPACE + /* All kernel objects within are assumed to be either completely + * initialized at build time, or initialized automatically at runtime + * via iteration before the POST_KERNEL phase. + * + * These two symbols only used by gen_kobject_list.py + */ + + _static_kernel_objects_begin = .; +#endif /* CONFIG_USERSPACE */ + SECTION_DATA_PROLOGUE(_k_timer_area, (OPTIONAL), SUBALIGN(4)) { _k_timer_list_start = .; @@ -176,3 +187,7 @@ KEEP(*(SORT_BY_NAME(".net_l2.data*"))) __net_l2_data_end = .; } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) + +#ifdef CONFIG_USERSPACE + _static_kernel_objects_end = .; +#endif diff --git a/include/linker/kobject-rom.ld b/include/linker/kobject-rom.ld new file mode 100644 index 00000000000..66f5a4ec300 --- /dev/null +++ b/include/linker/kobject-rom.ld @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef CONFIG_USERSPACE + /* Kept in RAM on non-XIP */ +#ifdef CONFIG_XIP + *(".kobject_data.rodata*") +#endif +#endif /* CONFIG_USERSPACE */ + diff --git a/include/linker/kobject-text.ld b/include/linker/kobject-text.ld new file mode 100644 index 00000000000..04671667a82 --- /dev/null +++ b/include/linker/kobject-text.ld @@ -0,0 +1,25 @@ +#ifndef KOBJECT_TEXT_AREA +#if defined(CONFIG_DEBUG) || defined(CONFIG_STACK_CANARIES) +#define KOBJECT_TEXT_AREA 256 +#else +#define KOBJECT_TEXT_AREA 78 +#endif +#endif + +#ifdef CONFIG_USERSPACE + /* We need to reserve room for the gperf generated hash functions. + * Fortunately, unlike the data tables, the size of the code is + * reasonably predictable; on x86 usually about 44 bytes with -Os. + * + * The linker will error out complaining that the location pointer + * is moving backwards if the reserved room isn't large enough. + */ + _kobject_text_area_start = .; + *(".kobject_data.text*") + _kobject_text_area_end = .; +#ifndef LINKER_PASS2 + PROVIDE(_k_object_find = .); +#endif + . += KOBJECT_TEXT_AREA - (_kobject_text_area_end - _kobject_text_area_start); +#endif /* CONFIG_USERSPACE */ + diff --git a/include/linker/kobject.ld b/include/linker/kobject.ld new file mode 100644 index 00000000000..be81a983d15 --- /dev/null +++ b/include/linker/kobject.ld @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef CONFIG_USERSPACE + /* Constraints: + * + * - changes to the size of this section between build phases + * *must not* shift the memory address of any kernel obejcts, + * since it contains a hashtable of the memory addresses of those + * kernel objects + * + * - It is OK if this section itself is shifted in between builds; for + * example some arches may precede this section with generated MMU + * page tables which are also unpredictable in size. + * + * The size of the + * gperf tables is both a function of the number of kernel objects, + * *and* the specific memory addresses being hashed. It is not something + * that can be predicted without actually building and compling it. + */ + SECTION_DATA_PROLOGUE(kobject_data, (OPTIONAL),) + { + *(".kobject_data.data*") + + /* This is also unpredictable in size, and has the same constraints. + * On XIP systems this will get put at the very end of ROM. + */ +#ifndef CONFIG_XIP + *(".kobject_data.rodata*") +#endif + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#endif /* CONFIG_USERSPACE */ + diff --git a/kernel/Kconfig b/kernel/Kconfig index b599a9f18d8..08e57a3fe33 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -354,6 +354,26 @@ config NUM_PIPE_ASYNC_MSGS Setting this option to 0 disables support for asynchronous pipe messages. + +config USERSPACE + bool "Enable user-level threads" + default n + help + When enabled, threads may be created or dropped down to user mode, + which has significantly restricted permissions and must interact + with the kernel via system calls. See Zephyr documentation for more + details about this feature. + +config MAX_THREAD_BYTES + int "Bytes to use when tracking object thread permissions" + default 2 + depends on USERSPACE + help + Every kernel object will have an associated bitfield to store + thread permissions for that object. This controls the size of the + bitfield (in bytes) and imposes a limit on how many threads can + be created in the system. + endmenu menu "Memory Pool Options" diff --git a/kernel/Makefile b/kernel/Makefile index 3757d551528..882c26b076e 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -37,3 +37,4 @@ lib-$(CONFIG_SYS_CLOCK_EXISTS) += timer.o lib-$(CONFIG_ATOMIC_OPERATIONS_C) += atomic_c.o lib-$(CONFIG_POLL) += poll.o lib-$(CONFIG_PTHREAD_IPC) += pthread.o +lib-$(CONFIG_USERSPACE) += userspace.o diff --git a/kernel/alert.c b/kernel/alert.c index d71c76c904a..25292ceaaaf 100644 --- a/kernel/alert.c +++ b/kernel/alert.c @@ -69,6 +69,8 @@ void k_alert_init(struct k_alert *alert, k_alert_handler_t handler, alert->work_item = (struct k_work)_K_WORK_INITIALIZER(_alert_deliver); k_sem_init(&alert->sem, 0, max_num_pending_alerts); SYS_TRACING_OBJ_INIT(k_alert, alert); + + _k_object_init(alert); } void k_alert_send(struct k_alert *alert) diff --git a/kernel/init.c b/kernel/init.c index 37d34ad55c3..fc654a48c3f 100644 --- a/kernel/init.c +++ b/kernel/init.c @@ -300,6 +300,7 @@ static void prepare_multithreading(struct k_thread *dummy_thread) CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL); _mark_thread_as_started(_main_thread); _add_thread_to_ready_q(_main_thread); + _k_object_init(_main_thread); #ifdef CONFIG_MULTITHREADING _new_thread(_idle_thread, _idle_stack, @@ -307,6 +308,7 @@ static void prepare_multithreading(struct k_thread *dummy_thread) K_LOWEST_THREAD_PRIO, K_ESSENTIAL); _mark_thread_as_started(_idle_thread); _add_thread_to_ready_q(_idle_thread); + _k_object_init(_idle_thread); #endif initialize_timeouts(); @@ -351,8 +353,11 @@ FUNC_NORETURN void _Cstart(void) #ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN struct k_thread *dummy_thread = NULL; #else - struct k_thread dummy_thread_memory; - struct k_thread *dummy_thread = &dummy_thread_memory; + /* Normally, kernel objects are not allowed on the stack, special case + * here since this is just being used to bootstrap the first _Swap() + */ + char dummy_thread_memory[sizeof(struct k_thread)]; + struct k_thread *dummy_thread = (struct k_thread *)&dummy_thread_memory; #endif /* diff --git a/kernel/mem_slab.c b/kernel/mem_slab.c index a0af3b98b91..d13c3d021ec 100644 --- a/kernel/mem_slab.c +++ b/kernel/mem_slab.c @@ -79,6 +79,8 @@ void k_mem_slab_init(struct k_mem_slab *slab, void *buffer, create_free_list(slab); sys_dlist_init(&slab->wait_q); SYS_TRACING_OBJ_INIT(k_mem_slab, slab); + + _k_object_init(slab); } int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, s32_t timeout) diff --git a/kernel/msg_q.c b/kernel/msg_q.c index baf0e022da5..7e58dd31407 100644 --- a/kernel/msg_q.c +++ b/kernel/msg_q.c @@ -58,6 +58,8 @@ void k_msgq_init(struct k_msgq *q, char *buffer, q->used_msgs = 0; sys_dlist_init(&q->wait_q); SYS_TRACING_OBJ_INIT(k_msgq, q); + + _k_object_init(q); } int k_msgq_put(struct k_msgq *q, void *data, s32_t timeout) diff --git a/kernel/mutex.c b/kernel/mutex.c index 3bf1d3d1a4f..7f9651fb8a5 100644 --- a/kernel/mutex.c +++ b/kernel/mutex.c @@ -77,6 +77,7 @@ void k_mutex_init(struct k_mutex *mutex) sys_dlist_init(&mutex->wait_q); SYS_TRACING_OBJ_INIT(k_mutex, mutex); + _k_object_init(mutex); } static int new_prio_for_inheritance(int target, int limit) diff --git a/kernel/pipes.c b/kernel/pipes.c index c6cc518ed6b..9b71d1d8c15 100644 --- a/kernel/pipes.c +++ b/kernel/pipes.c @@ -136,6 +136,7 @@ void k_pipe_init(struct k_pipe *pipe, unsigned char *buffer, size_t size) sys_dlist_init(&pipe->wait_q.writers); sys_dlist_init(&pipe->wait_q.readers); SYS_TRACING_OBJ_INIT(k_pipe, pipe); + _k_object_init(pipe); } /** diff --git a/kernel/sem.c b/kernel/sem.c index 43468f6acfc..4970113e52c 100644 --- a/kernel/sem.c +++ b/kernel/sem.c @@ -65,6 +65,8 @@ void k_sem_init(struct k_sem *sem, unsigned int initial_count, #endif SYS_TRACING_OBJ_INIT(k_sem, sem); + + _k_object_init(sem); } diff --git a/kernel/stack.c b/kernel/stack.c index 01dd4c1b1ec..fcfd94eff43 100644 --- a/kernel/stack.c +++ b/kernel/stack.c @@ -51,6 +51,7 @@ void k_stack_init(struct k_stack *stack, u32_t *buffer, int num_entries) stack->top = stack->base + num_entries; SYS_TRACING_OBJ_INIT(k_stack, stack); + _k_object_init(stack); } void k_stack_push(struct k_stack *stack, u32_t data) diff --git a/kernel/thread.c b/kernel/thread.c index f02bb97463a..3b4a89b9bb0 100644 --- a/kernel/thread.c +++ b/kernel/thread.c @@ -258,6 +258,7 @@ k_tid_t k_thread_create(struct k_thread *new_thread, __ASSERT(!_is_in_isr(), "Threads may not be created in ISRs"); _new_thread(new_thread, stack, stack_size, entry, p1, p2, p3, prio, options); + _k_object_init(new_thread); schedule_new_thread(new_thread, delay); return new_thread; @@ -424,6 +425,7 @@ void _init_static_threads(void) thread_data->init_options); thread_data->init_thread->init_data = thread_data; + _k_object_init(thread_data->init_thread); } _sched_lock(); diff --git a/kernel/timer.c b/kernel/timer.c index 7c7605adcdc..dca0f577731 100644 --- a/kernel/timer.c +++ b/kernel/timer.c @@ -103,6 +103,8 @@ void k_timer_init(struct k_timer *timer, SYS_TRACING_OBJ_INIT(k_timer, timer); timer->user_data = NULL; + + _k_object_init(timer); } diff --git a/kernel/userspace.c b/kernel/userspace.c new file mode 100644 index 00000000000..e1221b0cd1d --- /dev/null +++ b/kernel/userspace.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include +#include +#include +#include + +/** + * Kernel object validation function + * + * Retrieve metadata for a kernel object. This function is implemented in + * the gperf script footer, see gen_kobject_list.py + * + * @param obj Address of kernel object to get metadata + * @return Kernel object's metadata, or NULL if the parameter wasn't the + * memory address of a kernel object + */ +extern struct _k_object *_k_object_find(void *obj); + +const char *otype_to_str(enum k_objects otype) +{ + /* -fdata-sections doesn't work right except in very very recent + * GCC and these literal strings would appear in the binary even if + * otype_to_str was omitted by the linker + */ +#ifdef CONFIG_PRINTK + switch (otype) { + case K_OBJ_ALERT: + return "k_alert"; + case K_OBJ_DELAYED_WORK: + return "k_delayed_work"; + case K_OBJ_MEM_SLAB: + return "k_mem_slab"; + case K_OBJ_MSGQ: + return "k_msgq"; + case K_OBJ_MUTEX: + return "k_mutex"; + case K_OBJ_PIPE: + return "k_pipe"; + case K_OBJ_SEM: + return "k_sem"; + case K_OBJ_STACK: + return "k_stack"; + case K_OBJ_THREAD: + return "k_thread"; + case K_OBJ_TIMER: + return "k_timer"; + case K_OBJ_WORK: + return "k_work"; + case K_OBJ_WORK_Q: + return "k_work_q"; + default: + return "?"; + } +#else + ARG_UNUSED(otype); + return NULL; +#endif +} + +/* Stub functions, to be filled in forthcoming patch sets */ + +static void set_thread_perms(struct _k_object *ko, struct k_thread *thread) +{ + ARG_UNUSED(ko); + ARG_UNUSED(thread); + + /* STUB */ +} + + +static int test_thread_perms(struct _k_object *ko) +{ + ARG_UNUSED(ko); + + /* STUB */ + + return 1; +} + +static int _is_thread_user(void) +{ + /* STUB */ + + return 0; +} + +void k_object_grant_access(void *object, struct k_thread *thread) +{ + struct _k_object *ko = _k_object_find(object); + + if (!ko) { + if (_is_thread_user()) { + printk("granting access to non-existent kernel object %p\n", + object); + k_oops(); + } else { + /* Supervisor threads may at times instantiate objects + * that ignore rules on where they can live. Such + * objects won't ever be usable from userspace, but + * we shouldn't explode. + */ + return; + } + } + + /* userspace can't grant access to objects unless it already has + * access to that object + */ + if (_is_thread_user() && !test_thread_perms(ko)) { + printk("insufficient permissions in current thread %p\n", + _current); + printk("Cannot grant access to %s %p for thread %p\n", + otype_to_str(ko->type), object, thread); + k_oops(); + } + set_thread_perms(ko, thread); +} + + +int _k_object_validate(void *obj, enum k_objects otype, int init) +{ + struct _k_object *ko; + + ko = _k_object_find(obj); + + if (!ko || ko->type != otype) { + printk("%p is not a %s\n", obj, otype_to_str(otype)); + return -EBADF; + } + + /* Uninitialized objects are not owned by anyone. However if an + * object is initialized, and the caller is from userspace, then + * we need to assert that the user thread has sufficient permissions + * to re-initialize. + */ + if (ko->flags & K_OBJ_FLAG_INITIALIZED && _is_thread_user() && + !test_thread_perms(ko)) { + printk("thread %p does not have permission on %s %p\n", + _current, otype_to_str(otype), obj); + return -EPERM; + } + + /* If we are not initializing an object, and the object is not + * initialized, we should freak out + */ + if (!init && !(ko->flags & K_OBJ_FLAG_INITIALIZED)) { + printk("%p used before initialization\n", obj); + return -EINVAL; + } + + return 0; +} + + +void _k_object_init(void *object) +{ + struct _k_object *ko; + + /* By the time we get here, if the caller was from userspace, all the + * necessary checks have been done in _k_object_validate(), which takes + * place before the object is initialized. + * + * This function runs after the object has been initialized and + * finalizes it + */ + + ko = _k_object_find(object); + if (!ko) { + /* Supervisor threads can ignore rules about kernel objects + * and may declare them on stacks, etc. Such objects will never + * be usable from userspace, but we shouldn't explode. + */ + return; + } + + memset(ko->perms, 0, CONFIG_MAX_THREAD_BYTES); + set_thread_perms(ko, _current); + + ko->flags |= K_OBJ_FLAG_INITIALIZED; +} + diff --git a/kernel/work_q.c b/kernel/work_q.c index 3ae6bfbab7a..0d9020186b1 100644 --- a/kernel/work_q.c +++ b/kernel/work_q.c @@ -50,9 +50,9 @@ void k_work_q_start(struct k_work_q *work_q, k_thread_stack_t stack, size_t stack_size, int prio) { k_queue_init(&work_q->queue); - k_thread_create(&work_q->thread, stack, stack_size, work_q_main, work_q, 0, 0, prio, 0, 0); + _k_object_init(work_q); } #ifdef CONFIG_SYS_CLOCK_EXISTS @@ -70,6 +70,8 @@ void k_delayed_work_init(struct k_delayed_work *work, k_work_handler_t handler) k_work_init(&work->work, handler); _init_timeout(&work->timeout, work_timeout); work->work_q = NULL; + + _k_object_init(work); } int k_delayed_work_submit_to_queue(struct k_work_q *work_q, diff --git a/scripts/gen_kobject_list.py b/scripts/gen_kobject_list.py new file mode 100755 index 00000000000..99674978f8f --- /dev/null +++ b/scripts/gen_kobject_list.py @@ -0,0 +1,443 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +import sys +import argparse +import pprint +import os +import struct +from distutils.version import LooseVersion + +import elftools +from elftools.elf.elffile import ELFFile +from elftools.dwarf import descriptions +from elftools.elf.sections import SymbolTableSection + +if LooseVersion(elftools.__version__) < LooseVersion('0.24'): + sys.stderr.write("pyelftools is out of date, need version 0.24 or later\n") + sys.exit(1) + +kobjects = { + "k_alert" : "K_OBJ_ALERT", + "k_delayed_work" : "K_OBJ_DELAYED_WORK", + "k_mem_slab" : "K_OBJ_MEM_SLAB", + "k_msgq" : "K_OBJ_MSGQ", + "k_mutex" : "K_OBJ_MUTEX", + "k_pipe" : "K_OBJ_PIPE", + "k_sem" : "K_OBJ_SEM", + "k_stack" : "K_OBJ_STACK", + "k_thread" : "K_OBJ_THREAD", + "k_timer" : "K_OBJ_TIMER", + "k_work" : "K_OBJ_WORK", + "k_work_q" : "K_OBJ_WORK_Q", + } + +DW_OP_addr = 0x3 +DW_OP_fbreg = 0x91 + +# Global type environment. Populated by pass 1. +type_env = {} + +# --- debug stuff --- + +scr = os.path.basename(sys.argv[0]) + +def debug(text): + if not args.verbose: + return + sys.stdout.write(scr + ": " + text + "\n") + +def error(text): + sys.stderr.write("%s ERROR: %s\n" % (scr, text)) + sys.exit(1) + +def debug_die(die, text): + fn, ln = get_filename_lineno(die) + + debug(str(die)) + debug("File '%s', line %d:" % (fn, ln)) + debug(" %s" % text) + + +# --- type classes ---- + +class ArrayType: + def __init__(self, offset, num_members, member_type): + self.num_members = num_members + self.member_type = member_type + self.offset = offset + + def __repr__(self): + return "" % (self.member_type, self.num_members) + + def has_kobject(self): + if self.member_type not in type_env: + return False + + return type_env[self.member_type].has_kobject() + + def get_kobjects(self, addr): + mt = type_env[self.member_type] + objs = [] + + for i in range(self.num_members): + objs.extend(mt.get_kobjects(addr + (i * mt.size))) + return objs + + +class AggregateTypeMember: + def __init__(self, offset, member_name, member_type, member_offset): + self.member_name = member_name + self.member_type = member_type + self.member_offset = member_offset + + def __repr__(self): + return "" % (self.member_name, + self.member_type, self.member_offset) + + def has_kobject(self): + if self.member_type not in type_env: + return False + + return type_env[self.member_type].has_kobject() + + def get_kobjects(self, addr): + mt = type_env[self.member_type] + return mt.get_kobjects(addr + self.member_offset) + + +class AggregateType: + def __init__(self, offset, name, size): + self.name = name + self.size = size + self.offset = offset + self.members = [] + + def add_member(self, member): + self.members.append(member) + + def __repr__(self): + return "" % (self.name, self.members) + + def has_kobject(self): + result = False + + bad_members = [] + + for member in self.members: + if member.has_kobject(): + result = True + else: + bad_members.append(member) + # Don't need to consider this again, just remove it + + for bad_member in bad_members: + self.members.remove(bad_member) + + return result + + def get_kobjects(self, addr): + objs = [] + for member in self.members: + objs.extend(member.get_kobjects(addr)) + return objs + + +class KobjectType: + def __init__(self, offset, name, size): + self.name = name + self.size = size + self.offset = offset + + def __repr__(self): + return "" % self.name + + def has_kobject(self): + return True + + def get_kobjects(self, addr): + return [(addr, kobjects[self.name])] + +# --- helper functions for getting data from DIEs --- + +def die_get_name(die): + if not 'DW_AT_name' in die.attributes: + return None + return die.attributes["DW_AT_name"].value.decode("utf-8") + + +def die_get_type_offset(die): + return die.attributes["DW_AT_type"].value + die.cu.cu_offset + + +def die_get_byte_size(die): + if not 'DW_AT_byte_size' in die.attributes: + return 0 + + return die.attributes["DW_AT_byte_size"].value + +def analyze_die_struct(die): + name = die_get_name(die) or "" + offset = die.offset + size = die_get_byte_size(die) + + # Incomplete type + if not size: + return + + if name not in kobjects: + at = AggregateType(offset, name, size) + type_env[offset] = at + + for child in die.iter_children(): + if child.tag != "DW_TAG_member": + continue + child_type = die_get_type_offset(child) + member_offset = child.attributes["DW_AT_data_member_location"].value + cname = die_get_name(child) or "" + m = AggregateTypeMember(child.offset, cname, child_type, + member_offset) + at.add_member(m) + + return + + type_env[offset] = KobjectType(offset, name, size) + + +def analyze_die_array(die): + type_offset = die_get_type_offset(die) + elements = 1 + size_found = False + + for child in die.iter_children(): + if child.tag != "DW_TAG_subrange_type": + continue + if "DW_AT_upper_bound" not in child.attributes: + continue + + ub = child.attributes["DW_AT_upper_bound"] + if not ub.form.startswith("DW_FORM_data"): + continue + + size_found = True + elements = elements * (ub.value + 1) + + if not size_found: + return + + type_env[die.offset] = ArrayType(die.offset, elements, type_offset) + + +def get_filename_lineno(die): + lp_header = die.dwarfinfo.line_program_for_CU(die.cu).header + files = lp_header["file_entry"] + includes = lp_header["include_directory"] + + fileinfo = files[die.attributes["DW_AT_decl_file"].value - 1] + filename = fileinfo.name.decode("utf-8") + filedir = includes[fileinfo.dir_index - 1].decode("utf-8") + + path = os.path.join(filedir, filename) + lineno = die.attributes["DW_AT_decl_line"].value + return (path, lineno) + + +def find_kobjects(elf, syms): + if not elf.has_dwarf_info(): + sys.stderr.write("ELF file has no DWARF information\n"); + sys.exit(1) + + kram_start = syms["__kernel_ram_start"] + kram_end = syms["__kernel_ram_end"] + + di = elf.get_dwarf_info() + + variables = [] + + # Step 1: collect all type information. + for CU in di.iter_CUs(): + CU_path = CU.get_top_DIE().get_full_path() + lp = di.line_program_for_CU(CU) + + for idx, die in enumerate(CU.iter_DIEs()): + # Unions are disregarded, kernel objects should never be union + # members since the memory is not dedicated to that object and + # could be something else + if die.tag == "DW_TAG_structure_type": + analyze_die_struct(die) + elif die.tag == "DW_TAG_array_type": + analyze_die_array(die) + elif die.tag == "DW_TAG_variable": + variables.append(die) + + # Step 2: filter type_env to only contain kernel objects, or structs and + # arrays of kernel objects + bad_offsets = [] + for offset, type_object in type_env.items(): + if not type_object.has_kobject(): + bad_offsets.append(offset) + + for offset in bad_offsets: + del type_env[offset] + + # Step 3: Now that we know all the types we are looking for, examine + # all variables + all_objs = [] + + # Gross hack, see below + work_q_found = False + + for die in variables: + name = die_get_name(die) + if not name: + continue + + type_offset = die_get_type_offset(die) + + # Is this a kernel object, or a structure containing kernel objects? + if type_offset not in type_env: + continue + + if "DW_AT_declaration" in die.attributes: + # FIXME: why does k_sys_work_q not resolve an address in the DWARF + # data??? Every single instance it finds is an extern definition + # but not the actual instance in system_work_q.c + # Is there something weird about how lib-y stuff is linked? + if name == "k_sys_work_q" and not work_q_found and name in syms: + addr = syms[name] + work_q_found = True + else: + continue + else: + if "DW_AT_location" not in die.attributes: + debug_die(die, "No location information for object '%s'; possibly stack allocated" + % name) + continue + + loc = die.attributes["DW_AT_location"] + if loc.form != "DW_FORM_exprloc": + debug_die(die, "kernel object '%s' unexpected location format" % name) + continue + + opcode = loc.value[0] + if opcode != DW_OP_addr: + + # Check if frame pointer offset DW_OP_fbreg + if opcode == DW_OP_fbreg: + debug_die(die, "kernel object '%s' found on stack" % name) + else: + debug_die(die, "kernel object '%s' unexpected exprloc opcode %s" + % (name, hex(opcode))) + continue + + addr = (loc.value[1] | (loc.value[2] << 8) | (loc.value[3] << 16) | + (loc.value[4] << 24)) + + if addr < kram_start or addr >= kram_end: + if addr == 0: + # Never linked; gc-sections deleted it + continue + + debug_die(die, "object '%s' found in invalid location %s" % + (name, hex(addr))); + continue + + type_obj = type_env[type_offset] + objs = type_obj.get_kobjects(addr) + all_objs.extend(objs) + + debug("symbol '%s' at %s contains %d object(s)" % (name, hex(addr), + len(objs))) + + debug("found %d kernel object instances total" % len(all_objs)) + return all_objs + + +header = """%compare-lengths +%define lookup-function-name _k_object_lookup +%language=ANSI-C +%struct-type +%{ +#include +#include +%} +struct _k_object; +%% +""" + + +# Different versions of gperf have different prototypes for the lookup function, +# best to implement the wrapper here. The pointer value itself is turned into +# a string, we told gperf to expect binary strings that are not NULL-terminated. +footer = """%% +struct _k_object *_k_object_find(void *obj) +{ + return _k_object_lookup((const char *)obj, sizeof(void *)); +} +""" + + +def write_gperf_table(fp, objs, static_begin, static_end): + fp.write(header) + + for obj_addr, obj_type in objs: + # pre-initialized objects fall within this memory range, they are + # either completely initialized at build time, or done automatically + # at boot during some PRE_KERNEL_* phase + initialized = obj_addr >= static_begin and obj_addr < static_end + + byte_str = struct.pack("I", obj_addr) + fp.write("\"") + for byte in byte_str: + val = "\\x%02x" % byte + fp.write(val) + + fp.write("\",{},%s,%s\n" % (obj_type, + "K_OBJ_FLAG_INITIALIZED" if initialized else "0")) + + fp.write(footer) + + +def get_symbols(obj): + for section in obj.iter_sections(): + if isinstance(section, SymbolTableSection): + return {sym.name: sym.entry.st_value + for sym in section.iter_symbols()} + + raise LookupError("Could not find symbol table") + + +def parse_args(): + global args + + parser = argparse.ArgumentParser(description = __doc__, + formatter_class = argparse.RawDescriptionHelpFormatter) + + parser.add_argument("-k", "--kernel", required=True, + help="Input zephyr ELF binary") + parser.add_argument("-o", "--output", required=True, + help="Output list of kernel object addresses for gperf use") + parser.add_argument("-v", "--verbose", action="store_true", + help="Print extra debugging information") + args = parser.parse_args() + + +def main(): + parse_args() + + with open(args.kernel, "rb") as fp: + elf = ELFFile(fp) + args.little_endian = elf.little_endian + syms = get_symbols(elf) + objs = find_kobjects(elf, syms) + + with open(args.output, "w") as fp: + write_gperf_table(fp, objs, syms["_static_kernel_objects_begin"], + syms["_static_kernel_objects_end"]) + +if __name__ == "__main__": + main() + diff --git a/scripts/process_gperf.py b/scripts/process_gperf.py new file mode 100755 index 00000000000..338872249ec --- /dev/null +++ b/scripts/process_gperf.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +import sys +import argparse +import os +import re +from distutils.version import LooseVersion + +# --- debug stuff --- + +""" +gperf C file post-processor + +We use gperf to build up a perfect hashtable of pointer values. The way gperf +does this is to create a table 'wordlist' indexed by a string repreesentation +of a pointer address, and then doing memcmp() on a string passed in for +comparison + +We are exclusively working with 4-byte pointer values. This script adjusts +the generated code so that we work with pointers directly and not strings. +This saves a considerable amount of space. +""" + +def debug(text): + if not args.verbose: + return + sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") + + +def error(text): + sys.stderr.write(os.path.basename(sys.argv[0]) + " ERROR: " + text + "\n") + sys.exit(1) + + +def warn(text): + sys.stdout.write(os.path.basename(sys.argv[0]) + " WARNING: " + text + "\n") + + +def reformat_str(match_obj): + addr_str = match_obj.group(0) + + # Nip quotes + addr_str = addr_str[1:-1] + addr_vals = [0, 0, 0, 0] + ctr = 3 + i = 0 + + while (True): + if i >= len(addr_str): + break + + if addr_str[i] == "\\": + if addr_str[i+1].isdigit(): + # Octal escape sequence + val_str = addr_str[i+1:i+4] + addr_vals[ctr] = int(val_str, 8) + i += 4 + else: + # Char value that had to be escaped by C string rules + addr_vals[ctr] = ord(addr_str[i+1]) + i += 2 + + else: + addr_vals[ctr] = ord(addr_str[i]) + i += 1 + + ctr -= 1 + + return "(char *)0x%02x%02x%02x%02x" % tuple(addr_vals) + +def process_line(line, fp): + if line.startswith("#"): + fp.write(line) + return + + # Set the lookup function to static inline so it gets rolled into + # _k_object_find(), nothing else will use it + if re.search("struct _k_object [*]$", line): + fp.write("static inline " + line) + return + + m = re.search("gperf version (.*) [*][/]$", line) + if m: + v = LooseVersion(m.groups()[0]) + v_lo = LooseVersion("3.0") + v_hi = LooseVersion("3.1") + if (v < v_lo or v > v_hi): + warn("gperf %s is not tested, versions %s through %s supported" % + (v, v_lo, v_hi)) + + # Replace length lookups with constant len of 4 since we're always + # looking at pointers + line = re.sub(r'lengthtable[[]key[]]', r'4', line) + + # Empty wordlist entries to have NULLs instead of "" + line = re.sub(r'[{]["]["][}]', r'{}', line) + + # Suppress a compiler warning since this table is no longer necessary + line = re.sub(r'static unsigned char lengthtable', + r'static unsigned char __unused lengthtable', line) + + # drop all use of register keyword, let compiler figure that out, + # we have to do this since we change stuff to take the address of some + # parameters + line = re.sub(r'register', r'', line) + + # Hashing the address of the string + line = re.sub(r"hash [(]str, len[)]", + r"hash((const char *)&str, len)", line) + + # Just compare pointers directly instead of using memcmp + if re.search("if [(][*]str", line): + fp.write(" if (str == s)\n") + return + + # Take the strings with the binary information for the pointer values, + # and just turn them into pointers + line = re.sub(r'["].*["]', reformat_str, line) + + fp.write(line) + +def parse_args(): + global args + + parser = argparse.ArgumentParser(description = __doc__, + formatter_class = argparse.RawDescriptionHelpFormatter) + + parser.add_argument("-i", "--input", required=True, + help="Input C file from gperf") + parser.add_argument("-o", "--output", required=True, + help="Output C file with processing done") + parser.add_argument("-v", "--verbose", action="store_true", + help="Print extra debugging information") + args = parser.parse_args() + + +def main(): + parse_args() + + with open(args.input, "r") as in_fp, open(args.output, "w") as out_fp: + for line in in_fp.readlines(): + process_line(line, out_fp) + + +if __name__ == "__main__": + main() diff --git a/scripts/sanitycheck b/scripts/sanitycheck index d20153345c1..f6bf875bf9c 100755 --- a/scripts/sanitycheck +++ b/scripts/sanitycheck @@ -482,7 +482,8 @@ class SizeCalculator: "_k_fifo_area", "_k_lifo_area", "_k_stack_area", "_k_msgq_area", "_k_mbox_area", "_k_pipe_area", "net_if", "net_if_event", "net_stack", "net_l2_data", - "_k_queue_area", "_net_buf_pool_area", "app_datas" ] + "_k_queue_area", "_net_buf_pool_area", "app_datas", + "kobject_data", "mmu_tables"] # These get copied into RAM only on non-XIP ro_sections = ["text", "ctors", "init_array", "reset", "rodata", "devconfig", "net_l2", "vector"]