|
| 1 | +// Program startup, for a program that carries no runtime of its own. |
| 2 | +// |
| 3 | +// The same object openkal-linux supplies, for an environment that supplies far |
| 4 | +// less. It belongs to the implementation for the same reason: every step is a |
| 5 | +// fact about what hands control over, and a consumer that contained these steps |
| 6 | +// would contain a copy of them per environment. |
| 7 | +// |
| 8 | +// ⚠️ WHAT FIRMWARE HANDS OVER, AND WHAT IT DOES NOT. |
| 9 | +// |
| 10 | +// A kernel starts a program with arguments on the stack, a thread pointer to |
| 11 | +// establish, program headers to report, and a stack already there. Firmware |
| 12 | +// starts an IMAGE: it jumps to the load address in supervisor mode with a hart |
| 13 | +// identifier in a0 and a device tree in a1, and nothing else. There is no |
| 14 | +// argument vector to find, because there is no one to have supplied one; and |
| 15 | +// there is no stack, because a stack is a region of the image's own memory and |
| 16 | +// only the image knows where it put one. |
| 17 | +// |
| 18 | +// So this file does two things the other does not have to: it makes the stack |
| 19 | +// exist, and it stops. And it does not do the thing the other spends most of |
| 20 | +// its length on, because there is nothing to read. |
| 21 | +// |
| 22 | +// ⚠️ THE STACK COMES FROM THE PROGRAM'S LINKER SCRIPT AND CANNOT COME FROM HERE. |
| 23 | +// |
| 24 | +// `__stack_top' is defined by the linker script the program supplies, because |
| 25 | +// where the stack goes is a statement about the image's layout and the image is |
| 26 | +// the program's. This package names the symbol and does not define it; a |
| 27 | +// program that links this object without a script that defines it is told so by |
| 28 | +// the linker, which is the right report. |
| 29 | +// |
| 30 | +// Control is handed on through `__libc_start_main', weakly, exactly as the |
| 31 | +// other does: a program written directly against openkal has no such symbol, |
| 32 | +// and then this file runs the initialisers and calls `main' itself. |
| 33 | +#ifdef OPENKAL_OPENSBI_STANDALONE |
| 34 | + |
| 35 | +#include <openkal/abort.h> |
| 36 | + |
| 37 | +extern "C" { |
| 38 | + |
| 39 | +int main(int, char**, char**); |
| 40 | + |
| 41 | +[[gnu::weak]] int __libc_start_main(int (*)(int, char**, char**), int, char**, |
| 42 | + void (*)(), void (*)(), void (*)()); |
| 43 | + |
| 44 | +[[noreturn]] void __okb_start_c(void); |
| 45 | + |
| 46 | +} |
| 47 | + |
| 48 | +namespace { |
| 49 | + |
| 50 | +using initialiser = void (*)(int, char**, char**); |
| 51 | + |
| 52 | +[[gnu::weak]] extern initialiser __preinit_array_start[]; |
| 53 | +[[gnu::weak]] extern initialiser __preinit_array_end[]; |
| 54 | +[[gnu::weak]] extern initialiser __init_array_start[]; |
| 55 | +[[gnu::weak]] extern initialiser __init_array_end[]; |
| 56 | + |
| 57 | +void run_initialisers() { |
| 58 | + // ⚠️ Only when no C library took the hand-over. One that did runs these |
| 59 | + // itself, and running them twice constructs every static object twice. |
| 60 | + static char* nothing = nullptr; |
| 61 | + for (initialiser* p = __preinit_array_start; p != __preinit_array_end; ++p) |
| 62 | + (*p)(0, ¬hing, ¬hing); |
| 63 | + for (initialiser* p = __init_array_start; p != __init_array_end; ++p) |
| 64 | + (*p)(0, ¬hing, ¬hing); |
| 65 | +} |
| 66 | + |
| 67 | +} // namespace |
| 68 | + |
| 69 | +// The stack, then C. `la` of a linker-defined symbol resolves at link time, so |
| 70 | +// the sequence needs nothing to have been set up before it runs --- which is |
| 71 | +// the situation it is in. |
| 72 | +// |
| 73 | +// ⚠️ In `.text.entry` rather than `.text`, because the linker script places |
| 74 | +// that section first. Firmware jumps to the load address, not to `_start`: the |
| 75 | +// entry recorded in the image header is not read by firmware that loads a raw |
| 76 | +// image, so the first instruction at the load address has to BE this one. |
| 77 | +asm(".section .text.entry\n" |
| 78 | + ".globl _start\n" |
| 79 | + ".type _start,@function\n" |
| 80 | + "_start:\n" |
| 81 | + " la sp, __stack_top\n" |
| 82 | + " call __okb_start_c\n" |
| 83 | + "1: j 1b\n" |
| 84 | + ".size _start,.-_start\n"); |
| 85 | + |
| 86 | +extern "C" [[noreturn]] void __okb_start_c(void) { |
| 87 | + // Zero and null rather than a vector: openkal.env is what a program above |
| 88 | + // this reads its arguments through, and this environment's answer there is |
| 89 | + // that there are none. Passing the same answer here keeps the two from |
| 90 | + // disagreeing. |
| 91 | + static char* nothing = nullptr; |
| 92 | + |
| 93 | + if (__libc_start_main != nullptr) { |
| 94 | + __libc_start_main(main, 0, ¬hing, nullptr, nullptr, nullptr); |
| 95 | + // A C library's hand-over does not return. Reaching here means one did, |
| 96 | + // and continuing would run the program a second time. |
| 97 | + kal_exit(127); |
| 98 | + } |
| 99 | + |
| 100 | + run_initialisers(); |
| 101 | + kal_exit(main(0, ¬hing, ¬hing)); |
| 102 | +} |
| 103 | + |
| 104 | +#endif // OPENKAL_OPENSBI_STANDALONE |
0 commit comments