{"id":81,"date":"2025-09-19T23:01:13","date_gmt":"2025-09-19T21:01:13","guid":{"rendered":"https:\/\/microsproject.dev\/?p=81"},"modified":"2025-09-19T23:03:53","modified_gmt":"2025-09-19T21:03:53","slug":"micros-boots-successfully-on-qemu","status":"publish","type":"post","link":"https:\/\/microsproject.dev\/index.php\/2025\/09\/19\/micros-boots-successfully-on-qemu\/","title":{"rendered":"MicrOS boots successfully on QEMU \ud83c\udf89"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">For the last couple of days I\u2019ve been battling with my custom embedded operating system \u2014 <strong>MicrOS<\/strong> (slogan: <em>\u201csimple, open, embedded\u201d<\/em>). The goal was straightforward: get a bare-metal \u201cHello, world\u201d sample running under emulation, before moving to real hardware. The journey turned out to be more educational than I expected.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The challenge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My first target was the familiar <strong>STM32VLDiscovery<\/strong> board.<br>I wrote a minimal <strong>linker script<\/strong> and a <strong>startup file<\/strong> in C that set up the stack, copied <code>.data<\/code>, zeroed <code>.bss<\/code>, and jumped into <code>main()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The linker defined the memory regions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MEMORY {\n  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 1024K\n  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 128K\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the startup provided the vector table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>__attribute__((section(\".isr_vector\")))\nconst void *vector_table&#91;] = {\n    (void *)&amp;__stack_top__,  \/\/ initial SP\n    Reset_Handler,           \/\/ entry point\n    NMI_Handler,\n    HardFault_Handler,\n    \/\/ ...\n};\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything looked fine\u2026 until I tried to run it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>qemu-system-arm -M stm32vldiscovery -nographic -serial mon:stdio \\\n  -kernel build\/samples\/hello_world\/hello_world\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">QEMU answered:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>qemu: fatal: Lockup: can't escalate 3 to HardFault (current priority -1)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udca5 A hard crash, every time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The investigation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I assumed it was my fault (it usually is \ud83d\ude05).<br>I checked the ELF with <code>nm<\/code> and <code>objdump<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>arm-none-eabi-nm hello_world | grep __data\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>20000000 D __data_start__\n20000060 D __data_end__\n08001ad8 A __data_load__\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This revealed the first bug:<br><code>__data_load__<\/code> overlapped with <code>.text<\/code>, so my <code>Reset_Handler<\/code> was copying <em>instructions<\/em> as if they were data \u2014 instant fault!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">After fixing the linker script with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.data : {\n  __data_start__ = .;\n  *(.data*)\n  __data_end__ = .;\n} &gt; RAM AT &gt; FLASH\n\n__data_load__ = LOADADDR(.data);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the symbols looked healthy: <code>.data<\/code> in RAM, <code>.data_load__<\/code> in FLASH.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But the crash persisted.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The discovery<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The culprit wasn\u2019t my code after all \u2014 it was <strong>QEMU\u2019s STM32 emulation<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>stm32vldiscovery<\/code> machine in QEMU is only partially implemented. Touching even basic RCC or FLASH registers triggers faults that can\u2019t be recovered. That explained the mysterious HardFault loop.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The breakthrough<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of fighting STM32 emulation, I tried another board:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>qemu-system-arm -M lm3s6965evb -nographic \\\n  -kernel build\/samples\/hello_world\/hello_world\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And\u2026 success! \ud83c\udf89<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">MicrOS booted cleanly, initialized <code>.bss<\/code> and <code>.data<\/code>, and reached <code>main()<\/code> without crashing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To confirm, I inspected the vector table:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>arm-none-eabi-objdump -s -j .isr_vector hello_world | head\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>08000000  a8310020 c5010008 bd010008 ...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Decoded as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>0x200031a8<\/code> \u2192 initial SP (in SRAM \u2705)<\/li>\n\n\n\n<li><code>0x080001c5<\/code> \u2192 Reset_Handler (in FLASH \u2705)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The system was now <strong>booting properly<\/strong> in QEMU.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What this means for MicrOS<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Toolchain and startup are solid.<\/li>\n\n\n\n<li>\u2705 QEMU can run MicrOS reliably on <strong>LM3S6965EVB<\/strong>.<\/li>\n\n\n\n<li>\u274c QEMU\u2019s STM32 machines are not practical (real hardware will be the true test).<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-step: reproduce this yourself \ud83d\udee0\ufe0f<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Configure and build MicrOS<\/strong>: <code>rm -rf build\/ cmake -S . -B build\/ -DMICROS_BOARD=lm\/lm3s6965evb -DMICROS_SAMPLE=hello_world cmake --build build\/<\/code><\/li>\n\n\n\n<li><strong>Check binary size<\/strong>: <code>arm-none-eabi-size build\/samples\/hello_world\/hello_world<\/code> Example output: <code>text data bss dec hex filename 6805 160 12616 19581 4c7d build\/samples\/hello_world\/hello_world<\/code><\/li>\n\n\n\n<li><strong>Inspect vector table<\/strong>: <code>arm-none-eabi-objdump -s -j .isr_vector build\/samples\/hello_world\/hello_world | head<\/code> Make sure the first word is an SRAM address (SP), second is FLASH address (Reset_Handler).<\/li>\n\n\n\n<li><strong>Run in QEMU<\/strong>: <code>qemu-system-arm -M lm3s6965evb -nographic \\ -kernel build\/samples\/hello_world\/hello_world<\/code> You should see the firmware boot without crashing.<br>(Output from <code>printf()<\/code> will appear once <code>_write()<\/code> is hooked to UART0.)<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Next steps<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Implement <code>_write()<\/code> for UART0 on LM3S \u2192 <code>printf(\"Hello, MicrOS!\\n\")<\/code> visible in the QEMU terminal.<\/li>\n\n\n\n<li>Add the first <strong>task scheduler<\/strong> and <strong>context switching<\/strong>.<\/li>\n\n\n\n<li>Separate <code>arch\/<\/code> vs. <code>board\/<\/code> directories to make LM3S (for emulation) and STM32 (for hardware) selectable in CMake.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This milestone marks the first time <strong>MicrOS<\/strong> boots and runs on real (emulated) silicon.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s a small step, but a foundational one \u2014 proving the toolchain, linker, and startup are working correctly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next blog post will hopefully show a clean <code>\"Hello, MicrOS!\"<\/code> coming out of QEMU\u2019s UART, and then we can dive into scheduling and threads \ud83d\ude80.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n","protected":false},"excerpt":{"rendered":"<p>For the last couple of days I\u2019ve been battling with my custom embedded operating system \u2014 MicrOS (slogan: \u201csimple, open, embedded\u201d). The goal was straightforward: get a bare-metal \u201cHello, world\u201d sample running under emulation, before moving to real hardware. The journey turned out to be more educational than I expected. The challenge My first target [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-81","post","type-post","status-publish","format-standard","hentry","category-development"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/81","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/comments?post=81"}],"version-history":[{"count":1,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/81\/revisions"}],"predecessor-version":[{"id":82,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/posts\/81\/revisions\/82"}],"wp:attachment":[{"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/media?parent=81"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/categories?post=81"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/microsproject.dev\/index.php\/wp-json\/wp\/v2\/tags?post=81"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}