|
| 1 | +/* The memory map of a program this implementation starts, and the symbols its |
| 2 | + * startup object reads. |
| 3 | + * |
| 4 | + * ⭐⭐ THIS FILE IS A BOARD FACT AND THEREFORE BELONGS TO THIS PACKAGE. |
| 5 | + * |
| 6 | + * Until now every program that wanted to run over OpenSBI carried its own copy: |
| 7 | + * `examples/hello` had one, `openkal-llvm-runtime/examples/same-source` had a |
| 8 | + * second, and the specification's conformance suite would have needed a third. |
| 9 | + * The load address is not a property of any of them — it is where this firmware |
| 10 | + * hands control over, which is precisely what this package exists to know. |
| 11 | + * |
| 12 | + * ⚠️ Measured 2026-08-23: the conformance suite BUILDS for `riscv64-none-elf` |
| 13 | + * against this implementation and then produces an image whose entry point is |
| 14 | + * 0x0, because nothing placed it. The suite is written to run in a program that |
| 15 | + * carries no other runtime, and asking it to carry a board's memory map would |
| 16 | + * contradict that — clause 9 makes behavioural conformance a property of every |
| 17 | + * implementation, including one of a machine with no operating system. |
| 18 | + * |
| 19 | + * ⇒ Supplied through `build.mcpp`, which reaches the CONSUMER's link line. A |
| 20 | + * program adds nothing and gets a layout that works. |
| 21 | + * |
| 22 | + * ⚠️ THE SIZES ARE GENEROUS RATHER THAN MINIMAL, and that is the one judgement |
| 23 | + * in this file. A C program printing a string needs neither 256 KiB of stack |
| 24 | + * nor 16 MiB of heap; a program carrying a C library and a C++ standard library |
| 25 | + * does, and it exhausts the smaller figures during its own initialisation — |
| 26 | + * before `main`, so what it looks like is a program that starts and prints |
| 27 | + * nothing. Both are past the end of the image and cost nothing in it, and |
| 28 | + * QEMU's `virt` has 128 MiB. A board with far less can state its own. |
| 29 | + * |
| 30 | + * What follows is what a C++ program needs the layout to contain. |
| 31 | + * |
| 32 | + * OpenSBI occupies 0x80000000 upward and hands control to the next stage at |
| 33 | + * 0x80200000, which is why this address and not the start of RAM. |
| 34 | + * |
| 35 | + * ⚠️ FIRMWARE JUMPS TO THE LOWEST LOADED ADDRESS AND NOT TO THE ENTRY THE IMAGE |
| 36 | + * RECORDS. Measured 2026-08-23, after an attempt to put the ELF header inside |
| 37 | + * the first loaded segment so that `__ehdr_start` would be usable: the entry |
| 38 | + * moved to 0x80200270, OpenSBI still announced `Next Address 0x80200000`, and |
| 39 | + * the machine hung executing the header as instructions. So the first thing at |
| 40 | + * the load address has to be the first instruction, and everything that would |
| 41 | + * otherwise be read out of the program headers is named here instead. |
| 42 | + * |
| 43 | + * ⚠️ FOUR THINGS BEYOND THE MINIMUM, AND EACH IS SOMETHING A C++ PROGRAM HAS |
| 44 | + * THAT A C ONE DOES NOT. |
| 45 | + * |
| 46 | + * The initialiser arrays. Every static object with a constructor puts a |
| 47 | + * pointer in .init_array, and the C library walks it between symbols the |
| 48 | + * LINKER normally provides. A script that does not name them gets an empty |
| 49 | + * range and every such object stays unconstructed --- which does not fail, |
| 50 | + * it MISBEHAVES, and that is worse. |
| 51 | + * |
| 52 | + * The unwind tables, and the two symbols that bound them. libunwind built |
| 53 | + * with _LIBUNWIND_IS_BAREMETAL looks them up by name instead of walking |
| 54 | + * program headers; the fragment below is the one its AddressSpace.hpp |
| 55 | + * documents, reproduced rather than invented. |
| 56 | + * |
| 57 | + * The thread-local segment, and three symbols describing it. The startup |
| 58 | + * object in openkal-opensbi builds one context's storage from them. Without |
| 59 | + * it the register that names thread-local storage holds whatever reset left, |
| 60 | + * and the first `throw` faults inside `__cxa_get_globals` --- a message that |
| 61 | + * names an exception function and says nothing about a thread pointer. |
| 62 | + * |
| 63 | + * A stack big enough for the standard library. 16 KiB is enough for a program |
| 64 | + * that prints a string; formatting through <format> and sorting a vector is |
| 65 | + * not that program. |
| 66 | + */ |
| 67 | +ENTRY(_start) |
| 68 | +SECTIONS { |
| 69 | + . = 0x80200000; |
| 70 | + |
| 71 | + .text : { *(.text.entry) *(.text .text.*) } |
| 72 | + |
| 73 | + .rodata : { *(.rodata .rodata.*) *(.srodata .srodata.*) } |
| 74 | + |
| 75 | + /* Verbatim from libunwind's AddressSpace.hpp, which is where the names come |
| 76 | + * from. `--eh-frame-hdr` on the link line is what makes the index exist at |
| 77 | + * all; without it the section is empty and the two `_hdr_` symbols are the |
| 78 | + * zero the conditional below produces, which libunwind reads as "no index" |
| 79 | + * and falls back to scanning. */ |
| 80 | + .eh_frame : { |
| 81 | + __eh_frame_start = .; |
| 82 | + KEEP(*(.eh_frame)) |
| 83 | + __eh_frame_end = .; |
| 84 | + } |
| 85 | + .eh_frame_hdr : { KEEP(*(.eh_frame_hdr)) } |
| 86 | + __eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0; |
| 87 | + __eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0; |
| 88 | + |
| 89 | + .preinit_array : { |
| 90 | + PROVIDE_HIDDEN(__preinit_array_start = .); |
| 91 | + KEEP(*(.preinit_array)) |
| 92 | + PROVIDE_HIDDEN(__preinit_array_end = .); |
| 93 | + } |
| 94 | + .init_array : { |
| 95 | + PROVIDE_HIDDEN(__init_array_start = .); |
| 96 | + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*))) |
| 97 | + KEEP(*(.init_array)) |
| 98 | + PROVIDE_HIDDEN(__init_array_end = .); |
| 99 | + } |
| 100 | + .fini_array : { |
| 101 | + PROVIDE_HIDDEN(__fini_array_start = .); |
| 102 | + KEEP(*(SORT_BY_INIT_PRIORITY(.fini_array.*))) |
| 103 | + KEEP(*(.fini_array)) |
| 104 | + PROVIDE_HIDDEN(__fini_array_end = .); |
| 105 | + } |
| 106 | + |
| 107 | + /* The thread-local segment, and its three measurements. |
| 108 | + * |
| 109 | + * They are named here rather than read from PT_TLS for the reason at the |
| 110 | + * top of this file: the program headers are not reachable at run time on |
| 111 | + * this target. The startup object takes the initialised bytes from |
| 112 | + * `__tls_start`, copies `__tls_filesz` of them, and zeroes out to |
| 113 | + * `__tls_memsz` — which is what a loader does. */ |
| 114 | + .tdata : { __tls_start = .; *(.tdata .tdata.*) } |
| 115 | + .tbss : { *(.tbss .tbss.*) *(.tcommon) } |
| 116 | + __tls_filesz = SIZEOF(.tdata); |
| 117 | + __tls_memsz = SIZEOF(.tdata) + SIZEOF(.tbss); |
| 118 | + |
| 119 | + .data : { *(.data .data.*) *(.sdata .sdata.*) } |
| 120 | + |
| 121 | + .bss : { |
| 122 | + __bss_start = .; |
| 123 | + *(.sbss .sbss.*) *(.bss .bss.*) *(COMMON) |
| 124 | + __bss_end = .; |
| 125 | + } |
| 126 | + |
| 127 | + /* 256 KiB of stack. See the note above. */ |
| 128 | + . = ALIGN(16); . = . + 0x40000; __stack_top = .; |
| 129 | + |
| 130 | + /* ⭐ AND THE HEAP, WHICH FOR THIS PROGRAM CANNOT BE THE IMPLEMENTATION'S |
| 131 | + * DEFAULT. |
| 132 | + * |
| 133 | + * openkal-opensbi carries a 64 KiB static region for a program that |
| 134 | + * allocates a little. A program carrying a C library and a C++ standard |
| 135 | + * library allocates during its own initialisation, before `main`, and does |
| 136 | + * not survive that figure — measured, and what it looks like is a program |
| 137 | + * that starts and prints nothing, because the allocator runs out before |
| 138 | + * there is a stream to report it on. |
| 139 | + * |
| 140 | + * So the region is stated here, where the stack's size already is, and it |
| 141 | + * costs nothing in the image: it is past the end and QEMU's `virt` has |
| 142 | + * 128 MiB. 16 MiB is comfortably more than this program needs and small |
| 143 | + * enough to fit a machine with far less. */ |
| 144 | + . = ALIGN(16); __heap_start = .; . = . + 0x1000000; __heap_end = .; |
| 145 | +} |
0 commit comments