Skip to content

Commit

Permalink
Implement static linking w/ .data and .bss initialization.
Browse files Browse the repository at this point in the history
Proper compiler support for ROPI-RWPI relocation is a ways off; in the meantime, this lets Rust apps work on Tock.

The application will need to know where it will be loaded in RAM, which requires adjusting the kernel's linker script. To support the existing examples, I guessed where an application would be loaded on the nRF52-DK.

An application also needs to know where its .text will end up, which is immediately after the protected region. To make this predictable, we need to fix the protected region size. This is done using elf2tab's --protected-region-size, added in tock/elf2tab#6. Users of libtock-rs will need a recent elf2tab and must pass an appropriate value for protected-region-size to elf2tab.
  • Loading branch information
jrvanwhy committed Feb 6, 2019
1 parent 40e8ba7 commit 63bdba1
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 65 deletions.
5 changes: 3 additions & 2 deletions .cargo/config
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Target configuration for the example apps on the nRF52-DK
[target.thumbv7em-none-eabi]
rustflags = [
"-C", "link-arg=-Tlayout.ld",
"-C", "relocation-model=ropi-rwpi",
"-C", "link-arg=-Tnrf52_layout.ld",
"-C", "relocation-model=static",
"-D", "warnings",
]
75 changes: 38 additions & 37 deletions layout.ld
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
/* Userland Generic Layout
*
* This linker script is designed for Tock apps where the end microcontroller
* is not known. Therefore, this script over provisions space on some platforms.
* Currently, due to incomplete ROPI-RWPI support in rustc (see
* https://github.com/tock/libtock-rs/issues/28), this layout implements static
* linking. An application init script must define the TEXT and SRAM address
* ranges as well as MPU_MIN_ALIGN before including this layout file.
*
* Here is a an example application linker script to get started:
* MEMORY {
* /* TEXT must start immediately *after* the Tock Binary Format
* * headers, which means you need to offset the beginning of TEXT
* * relative to where the application is loaded.
* TEXT (rx) : ORIGIN = 0x10030, LENGTH = 0x0FFD0
* SRAM (RWX) : ORIGIN = 0x20000, LENGTH = 0x10000
* }
* MPU_MIN_ALIGN = 8K;
* INCLUDE ../libtock-rs/layout.ld
*/

STACK_SIZE = 2048;

ENTRY(_start)

/* Note: Because apps are relocated, the FLASH address here acts as a sentinel
* value for relocation fixup routines. The application loader will select the
* actual location in flash where the app is placed.
*/
MEMORY {
FLASH (rx) : ORIGIN = 0x80000000, LENGTH = 0x0040000
SRAM (RWX) : ORIGIN = 0x00000000, LENGTH = 0x0010000
}

SECTIONS {
/* Section for just the app crt0 header.
* This must be first so that the app can find it.
Expand Down Expand Up @@ -61,17 +65,11 @@ SECTIONS {
LONG(LOADADDR(.endflash) - _beginning);
/* The size of the stack requested by this application */
LONG(STACK_SIZE);
} > FLASH =0xFF

/* App state section. Used for persistent app data.
* We put this first so that if the app code changes but the persistent
* data doesn't, the app_state can be preserved.
*/
.wfr.app_state :
{
KEEP (*(.app_state))
. = ALIGN(4); /* Make sure we're word-aligned here */
} > FLASH =0xFF
/* Pad the header out to a multiple of 32 bytes so there is not a gap
* between the header and subsequent .data section. It's unclear why,
* but LLD is aligning sections to a multiple of 32 bytes. */
. = ALIGN(32);
} > TEXT =0xFF

/* Text section, Code! */
.text :
Expand All @@ -85,29 +83,32 @@ SECTIONS {
_etext = .;
*(.ARM.extab*)
. = ALIGN(4); /* Make sure we're word-aligned here */
} > FLASH =0xFF

/* Global Offset Table */
.got :
{
. = ALIGN(4); /* Make sure we're word-aligned here */
_got = .;
*(.got*)
*(.got.plt*)
. = ALIGN(4);
} > SRAM AT > FLASH
} > TEXT =0xFF

/* Data section, static initialized variables
* Note: This is placed in Flash after the text section, but needs to be
* moved to SRAM at runtime
*/
.data :
{
. = ALIGN(4); /* Make sure we're word-aligned here */
/* Account for MPU alignment and the stack */
. = ALIGN(MPU_MIN_ALIGN);
. = . + STACK_SIZE;
. = ALIGN(8); /* The stack is aligned to a multiple of 8 bytes. */
_data = .;
KEEP(*(.data*))
. = ALIGN(4); /* Make sure we're word-aligned at the end of flash */
} > SRAM AT > FLASH
} > SRAM

/* Global Offset Table */
.got :
{
. = ALIGN(4); /* Make sure we're word-aligned here */
_got = .;
*(.got*)
*(.got.plt*)
. = ALIGN(4);
} > SRAM

/* BSS section, static uninitialized variables */
.bss :
Expand All @@ -122,7 +123,7 @@ SECTIONS {
/* End of flash. */
.endflash :
{
} > FLASH
} > TEXT

/* ARM Exception support
*
Expand All @@ -142,6 +143,6 @@ SECTIONS {
{
/* (C++) Index entries for section unwinding */
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
} > TEXT
PROVIDE_HIDDEN (__exidx_end = .);
}
11 changes: 11 additions & 0 deletions nrf52_layout.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Layout for the nRF52-DK, used by the examples in this repository. */

MEMORY {
/* The application region is 64 bytes (0x40) */
TEXT (rx) : ORIGIN = 0x00020040, LENGTH = 0x0005FFC0
SRAM (rwx) : ORIGIN = 0x20002000, LENGTH = 62K
}

MPU_MIN_ALIGN = 8K;

INCLUDE layout.ld
2 changes: 1 addition & 1 deletion run_example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ tab_file_name="target/tab/$1.tab"
mkdir -p "target/tab/$1"
cp "target/thumbv7em-none-eabi/release/examples/$1" "$elf_file_name"

elf2tab -n "$1" -o "$tab_file_name" "$elf_file_name" --stack 2048 --app-heap 1024 --kernel-heap 1024
elf2tab -n "$1" -o "$tab_file_name" "$elf_file_name" --stack 2048 --app-heap 1024 --kernel-heap 1024 --protected-region-size=64

if [ "$#" -ge "2" ]
then
Expand Down
99 changes: 74 additions & 25 deletions src/entry_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ use core::alloc::Alloc;
use core::alloc::GlobalAlloc;
use core::alloc::Layout;
use core::intrinsics;
use core::mem;
use core::ptr;
use core::ptr::NonNull;

const HEAP_SIZE: usize = 0x400;

// None-threaded heap wrapper based on `r9` register instead of global variable
/// Memory allocation implementation for Tock applications. This must be
/// instantiated in the crate root.
pub(crate) struct TockAllocator;

impl TockAllocator {
// Retrieve access to the global linked_list_allocator::Heap instance.
unsafe fn heap(&self) -> &mut Heap {
let heap: *mut Heap;
asm!("mov $0, r9" : "=r"(heap) : : : "volatile");
&mut *heap
static mut HEAP: Heap = Heap::empty();
&mut HEAP
}

/// Initializes an empty heap
Expand All @@ -29,12 +27,7 @@ impl TockAllocator {
/// This function must be called at most once. The memory between [`heap_location`] and [`heap_top`] must not overlap with any other memory section.
#[inline(never)]
unsafe fn init(&mut self, heap_bottom: usize, heap_top: usize) {
asm!("mov r9, $0" : : "r"(heap_bottom) : : "volatile");

let effective_heap_bottom = heap_bottom + mem::size_of::<Heap>();

let heap = heap_bottom as *mut Heap;
*heap = Heap::new(effective_heap_bottom, heap_top - effective_heap_bottom);
self.heap().init(heap_bottom, heap_top - heap_bottom);
}
}

Expand All @@ -48,31 +41,31 @@ unsafe impl GlobalAlloc for TockAllocator {
}
}

// Note: At the moment, rust_start is incomplete. The rest of this comment
// describes how rust_start *should* work. It does not currently perform data
// relocation (note the TODO in rust_start's source).
//
// _start and rust_start are the first two procedures executed when a Tock
// application starts. _start is invoked directly by the Tock kernel; it
// performs stack setup then calls rust_start. rust_start performs data
// relocation and sets up the heap before calling the rustc-generated main.
// rust_start and _start are tightly coupled: the order of rust_start's
// parameters is designed to simplify _start's implementation.
//
// The memory layout set up by these methods is as follows:
// The memory layout is controlled by the linker script and these methods. These
// are written for the following memory layout:
//
// +----------------+ <- app_heap_break
// | Heap |
// +----------------| <- heap_bottom
// | Data (globals) |
// | .data and .bss |
// +----------------+ <- stack_top
// | Stack |
// | (grows down) |
// +----------------+ <- mem_start
//
// app_heap_break and mem_start are given to us by the kernel. The stack size is
// determined using pointer text_start, and is used with mem_start to compute
// stack_top.
// stack_top. The placement of .data and .bss are given to us by the linker
// script; the heap is located between the end of .bss and app_heap_break. This
// requires that .bss is the last (highest-address) section placed by the linker
// script.

/// Tock programs' entry point. Called by the kernel at program start. Sets up
/// the stack then calls rust_start() for the remainder of setup.
Expand All @@ -87,6 +80,25 @@ pub unsafe extern "C" fn _start(
app_heap_break: usize,
) -> ! {
asm!("
// Because ROPI-RWPI support in LLVM/rustc is incomplete, Rust
// applications must be statically linked. An offset between the
// location the program is linked at and its actual location in flash
// would cause references in .data and .rodata to point to the wrong
// data. To mitigate this, this section checks that .text (and .start)
// are loaded at the correct location. If the application was linked and
// loaded correctly, the location of the first instruction (read using
// the Program Counter) will match the intended location of .start. We
// don't have an easy way to signal an error, so for now we just yield
// if the location is wrong.
sub r4, pc, #4 // r4 = pc
ldr r5, =.start // r5 = address of .start
cmp r4, r5
beq .Lstack_init // Jump to stack initialization if pc was correct
.Lyield_loop:
svc 0 // yield() syscall
b .Lyield_loop
.Lstack_init:
// Initialize the stack pointer. The stack pointer is computed as
// stack_size + mem_start plus padding to align the stack to a multiple
// of 8 bytes. The 8 byte alignment is to follow ARM AAPCS:
Expand All @@ -108,28 +120,65 @@ pub unsafe extern "C" fn _start(
intrinsics::unreachable();
}

/// The header encoded at the beginning of .text by the linker script. It is
/// accessed by rust_start() using its text_start parameter.
#[repr(C)]
struct LayoutHeader {
got_sym_start: usize,
got_start: usize,
got_size: usize,
data_sym_start: usize,
data_start: usize,
data_size: usize,
bss_start: usize,
bss_size: usize,
reldata_start: usize,
stack_size: usize,
}

/// Rust setup, called by _start. Uses the extern "C" calling convention so that
/// the assembly in _start knows how to call it (the Rust ABI is not defined).
/// Sets up the data segment (including relocations) and the heap, then calls
/// into the rustc-generated main(). This cannot use mutable global variables or
/// global references to globals until it is done setting up the data segment.
#[no_mangle]
pub unsafe extern "C" fn rust_start(
_text_start: usize,
text_start: usize,
stack_top: usize,
_skipped: usize,
_app_heap_break: usize,
app_heap_break: usize,
) -> ! {
extern "C" {
// This function is created internally by`rustc`. See `src/lang_items.rs` for more details.
fn main(argc: isize, argv: *const *const u8) -> isize;
}

// TODO: Copy over .data and perform relocations, *then* initialize the heap.
TockAllocator.init(stack_top, stack_top + HEAP_SIZE);
// Copy .data into its final location in RAM (determined by the linker
// script -- should be immediately above the stack).
let layout_header: &LayoutHeader = core::mem::transmute(text_start);
intrinsics::copy_nonoverlapping(
(text_start + layout_header.data_sym_start) as *const u8,
stack_top as *mut u8,
layout_header.data_size,
);

// Zero .bss (specified by the linker script).
let bss_end = layout_header.bss_start + layout_header.bss_size; // 1 past the end of .bss
for i in layout_header.bss_start..bss_end {
core::ptr::write(i as *mut u8, 0);
}

// TODO: Wait for rustc to have working ROPI-RWPI relocation support, then
// implement dynamic relocations here. At the moment, rustc does not have
// working ROPI-RWPI support, and it is not clear what that support would
// look like at the LLVM level. Once we know what the relocation strategy
// looks like we can write the dynamic linker.

// Initialize the heap and tell the kernel where everything is. The heap is
// placed between .bss and the end of application memory.
TockAllocator.init(bss_end, app_heap_break);
syscalls::memop(10, stack_top);
syscalls::memop(11, stack_top + HEAP_SIZE);
syscalls::memop(11, bss_end);

main(0, ptr::null());

Expand Down

0 comments on commit 63bdba1

Please sign in to comment.