1import{_ as n,c as a,e,o as i}from"./app-CjUkjRdW.js";const l={};function t(p,s){return i(),a("div",null,s[0]||(s[0]=[e(`<h1 id="targeting-uefi-part-1" tabindex="-1"><a class="header-anchor" href="#targeting-uefi-part-1"><span>Targeting UEFI (Part 1)</span></a></h1><p>Traditionally, booting an operating system on x86/x86_64 hardware has been done using the BIOS. The BIOS has been considered legacy for a long time, and has been replaced by UEFI (Unified Extensible Firmware Interface) on most modern hardware. We no longer have to write a boot sector in assembly and rely on BIOS interrupts to load the OS. In this section we will focus on cross-compiling to UEFI (we'll get to the actual booting part later).</p><p>Since there is no OS to target yet, we'll need to cross-compile to a <strong>freestanding</strong> environment (as opposed to an OS hosted environment), where only a subset of the C standard library and runtime is available. That means we can't use features from the standard library that rely on OS support like memory allocation, threads, IO, etc.</p><div class="hint-container tip"><p class="hint-container-title">Goal Build a minimal UEFI executable using Nim. The executable should assume a</p><p>freestanding environment and does nothing but return 0 from the entry point.</p></div><h2 id="building-a-pe32-executable" tabindex="-1"><a class="header-anchor" href="#building-a-pe32-executable"><span>Building a PE32+ executable</span></a></h2><p>The first hurdle we have to overcome is that the UEFI firmware expects a PE32+ executable (Portable Executable with 64-bit extension to the standard PE32 format), which is an executable format used by Windows. It also expects the executable to follow the Windows ABI x64 calling convention. But since we're developing on Linux, we'll need a way to cross-compile our bootloader to this format.</p><p>Let's forget about Nim for a moment. Can we cross-compile a simple C program to a freestanding PE32+ executable on Linux? This is why we installed <code>clang</code> earlier, which supports multiple targets. The target we're interested in is <code>x86_64-unknown-windows</code> (the <code>unknown</code> part is for the vendor, which is not important in our case). We also need to tell the compiler that we don't have a standard library by passing the <code>-ffreestanding</code> flag:</p><div class="language-c line-numbers-mode" data-highlighter="prismjs" data-ext="c" data-title="c"><pre><code><span class="line"><span class="token comment">// main.c</span></span> 2<span class="line"></span> 3<span class="line"><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 4<span class="line"> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span></span> 5<span class="line"><span class="token punctuation">}</span></span> 6<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">clang <span class="token parameter variable">-c</span> <span class="token punctuation">\\</span></span> 7<span class="line"> <span class="token parameter variable">-target</span> x86_64-unknown-windows <span class="token punctuation">\\</span></span> 8<span class="line"> <span class="token parameter variable">-ffreestanding</span> <span class="token punctuation">\\</span></span> 9<span class="line"> <span class="token parameter variable">-o</span> build/main.o <span class="token punctuation">\\</span></span> 10<span class="line"> main.c</span></span></span> 11<span class="line"></span> 12<span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash"><span class="token function">file</span> build/main.o</span></span></span> 13<span class="line"><span class="token output">build/main.o: Intel amd64 COFF object file, not stripped, 6 sections, symbol offset=0x143, 16 symbols, created Thu Nov 30 02:47:57 2023, 1st section name ".text"</span> 14<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>We have a COFF object file, which is what PE32+ executables are based on. Now let's link it by telling <code>clang</code> to use the <code>lld-link</code> linker (which is the <code>lld</code> linker flavor that targets Windows):</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">
14clang <span class="token punctuation">\\</span></span> 15<span class="line"> <span class="token parameter variable">-target</span> x86_64-unknown-windows <span class="token punctuation">\\</span></span> 16<span class="line"> -fuse-ld<span class="token operator">=</span>lld-link <span class="token punctuation">\\</span></span> 17<span class="line"> <span class="token parameter variable">-o</span> build/main.exe <span class="token punctuation">\\</span></span> 18<span class="line"> build/main.o</span></span></span> 19<span class="line"><span class="token output">lld-link: error: could not open 'libcmt.lib': No such file or directory</span> 20<span class="line">lld-link: error: could not open 'oldnames.lib': No such file or directory</span> 21<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The linker is trying to statically link <code>libcmt.lib</code>, the native Windows CRT startup library, and <code>oldnames.lib</code>, a compatibility library for redirecting old function names to new ones. We're not going to rely on these default libraries, so we can tell the linker to exclude them by passing the <code>-nostdlib</code> flag:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">clang <span class="token punctuation">\\</span></span> 22<span class="line"> <span class="token parameter variable">-target</span> x86_64-unknown-windows <span class="token punctuation">\\</span></span> 23<span class="line"> -fuse-ld<span class="token operator">=</span>lld-link <span class="token punctuation">\\</span></span> 24<span class="line highlighted"> <span class="token parameter variable">-nostdlib</span> <span class="token punctuation">\\</span></span> 25<span class="line"> <span class="token parameter variable">-o</span> build/main.exe <span class="token punctuation">\\</span></span> 26<span class="line"> build/main.o</span></span></span> 27<span class="line"><span class="token output">lld-link: error: <root>: undefined symbol: mainCRTStartup</span> 28<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The linker is unable to find the C runtime entry point, <code>mainCRTStartup</code>, which makes sense because we're not linking the startup library. We can tell the linker to use our <code>main</code> function as the entry point by passing the <code>-entry:main</code> flag:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">clang <span class="token punctuation">\\</span></span> 29<span class="line"> <span class="token parameter variable">-target</span> x86_64-unknown-windows <span class="token punctuation">\\</span></span> 30<span class="line"> -fuse-ld<span class="token operator">=</span>lld-link <span class="token punctuation">\\</span></span> 31<span class="line"> <span class="token parameter variable">-nostdlib</span> <span class="token punctuation">\\</span></span> 32<span class="line highlighted"> -Wl,-entry:main <span class="token punctuation">\\</span></span> 33<span class="line"> <span class="token parameter variable">-o</span> build/main.exe <span class="token punctuation">\\</span></span> 34<span class="line"> build/main.o</span></span></span> 35<span class="line"></span> 36<span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash"><span class="token function">file</span> build/main.exe</span></span></span> 37<span class="line"><span class="token output">build/main.exe: PE32+ executable (console) x86-64, for MS Windows</span> 38<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Great! We have a PE32+ executable. But notice that it says <code>(console)</code>. This means that the executable is a console application, which cannot run on UEFI. We need to tell the linker to create a UEFI application instead by passing the <code>-subsystem:efi_application</code> flag:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">
38clang <span class="token punctuation">\\</span></span> 39<span class="line"> <span class="token parameter variable">-target</span> x86_64-unknown-windows <span class="token punctuation">\\</span></span> 40<span class="line"> -fuse-ld<span class="token operator">=</span>lld-link <span class="token punctuation">\\</span></span> 41<span class="line"> <span class="token parameter variable">-nostdlib</span> <span class="token punctuation">\\</span></span> 42<span class="line"> -Wl,-entry:main <span class="token punctuation">\\</span></span> 43<span class="line highlighted"> -Wl,-subsystem:efi_application <span class="token punctuation">\\</span></span> 44<span class="line"> <span class="token parameter variable">-o</span> build/main.exe <span class="token punctuation">\\</span></span> 45<span class="line"> build/main.o</span></span></span> 46<span class="line"></span> 47<span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash"><span class="token function">file</span> build/main.exe</span></span></span> 48<span class="line"><span class="token output">build/main.exe: PE32+ executable (EFI application) x86-64, for MS Windows</span> 49<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Now we have a true UEFI application.</p><h2 id="cross-compiling-nim-to-pe32" tabindex="-1"><a class="header-anchor" href="#cross-compiling-nim-to-pe32"><span>Cross-compiling Nim to PE32+</span></a></h2><p>Let's try to do the same thing with Nim. We'll port the C program to Nim:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># main.nim</span></span> 50<span class="line"></span> 51<span class="line"><span class="token keyword">proc</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> int <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 52<span class="line"> <span class="token keyword">return</span> <span class="token number">0</span></span> 53<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The <code>{.exportc.}</code> pragma tells the Nim compiler to export the function name as is, without any mangling. We do this because we need to pass the entry point name to the linker, and we don't want the compiler to mangle it.</p><p>Before we port this to Nim, we need to understand that Nim itself supports multiple targets. There are three arguments that influence the compilation/linking to a specific target:</p><ul><li><code>--cpu</code>(architecture), which defaults to the host architecture (in my case this is <code>amd64</code>, i.e. x86_64)</li><li><code>--os</code>(operating system), which defaults to the host operating system (in my case this is <code>linux</code>)</li><li><code>--cc</code>(backend compiler), which defaults to <code>gcc</code> (on Windows it relies on MinGW, which is a port of GCC to Windows)</li></ul><p>Nim does support cross-compiling to Windows using the <code>-d:mingw</code> flag. However, while the executable we want is a Windows executable format, the target OS is not Windows, but UEFI. Nim doesn't have a target OS for UEFI, so we'll need to use the <code>--os:any</code> flag to tell the compiler to not use any OS-specific code (it only expects a handful of ANSI C library functions to be available).</p><p>So, to cross-compile to UEFI, we need to set these three flags to: <code>--cpu:amd64</code>, <code>--os:any</code>, and <code>--cc:clang</code>. We also pass the <code>clang</code> flags we used earlier to the compiler and linker using the <code>--passc</code> and <code>--passl</code> flags respectively.</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c <span class="token punctuation">\\</span></span> 54<span class="line"> <span class="token parameter variable">--nimcache:build</span> <span class="token punctuation">\\</span></span> 55<span class="line"> <span class="token parameter variable">--cpu:amd64</span> <span class="token punctuation">\\</span></span> 56<span class="line"> <span class="token parameter variable">--os:any</span> <span class="token punctuation">\\</span></span> 57<span class="line"> <span class="token parameter variable">--cc:clang</span> <span class="token punctuation">\\</span></span> 58<span class="line"> --passc:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 59<span class="line"> --passc:<span class="token string">"-ffreestanding"</span> <span class="token punctuation">\\</span></span> 60<span class="line"> --passl:<span class="token string">"-fuse-ld=lld-link"</span> <span class="token punctuation">\\</span></span> 61<span class="line"> --passl:<span class="token string">"-nostdlib"</span> <span class="token punctuation">\\</span></span> 62<span class="line"> --passl:<span class="token string">"-Wl,-entry:main"</span> <span class="token punctuation">\\</span></span> 63<span class="line"> --passl:<span class="token string">"-Wl,-subsystem:efi_application"</span> <span class="token punctuation">\\</span></span> 64<span class="line"> --out:build/main.exe <span class="token punctuation">\\</span></span> 65<span class="line"> main.nim</span></span></span> 66<span class="line"><span class="token output">.../lib/system/osalloc.nim(218, 10) Error: Port memory manager to your platform</span> 67<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The compiler is complaining that it doesn't know how to allocate memory on this platform. This makes sense because we're not targeting any OS. Since we don't have an OS yet, we need a way to provide memory allocation primitives to the Nim compiler. The Nim docs
67say:</p><blockquote><p>The <code>-d:useMalloc</code> option configures Nim to use only the standard C memory manage primitives <code>malloc()</code>, <code>free()</code>, <code>realloc()</code>. If your platform does not provide these functions it should be trivial to provide an implementation for them and link these to your program.</p></blockquote><p>OK, at least we have a way to provide memory allocation primitives to Nim, instead of assuming they're provided by an existing OS (e.g. <code>mmap</code> on Linux or <code>VirtualAlloc</code> on Windows). Since we don't have an OS yet, let's implement a simple bump allocator backed by a fixed-size buffer. To keep things simple, we will not worry about freeing memory for now (we'll get to that later when we implement a proper memory manager).</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># malloc.nim</span></span> 68<span class="line"></span> 69<span class="line"><span class="token punctuation">{.</span>used<span class="token punctuation">.}</span></span> 70<span class="line"></span> 71<span class="line"><span class="token keyword">var</span></span> 72<span class="line"> heap<span class="token operator">*:</span> array<span class="token punctuation">[</span><span class="token number">1</span><span class="token operator">*</span><span class="token number">1024</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">,</span> byte<span class="token punctuation">]</span> <span class="token comment"># 1 MiB heap</span></span> 73<span class="line"> heapBumpPtr<span class="token operator">*:</span> int <span class="token operator">=</span> <span class="token function">cast[int]</span><span class="token punctuation">(</span><span class="token keyword">addr</span> heap<span class="token punctuation">)</span></span> 74<span class="line"> heapMaxPtr<span class="token operator">*:</span> int <span class="token operator">=</span> <span class="token function">cast[int]</span><span class="token punctuation">(</span><span class="token keyword">addr</span> heap<span class="token punctuation">)</span> <span class="token operator">+</span> heap<span class="token operator">.</span>high</span> 75<span class="line"></span> 76<span class="line"><span class="token keyword">proc</span> <span class="token function">malloc<span class="token operator">*</span></span><span class="token punctuation">(</span>size<span class="token operator">:</span> csize_t<span class="token punctuation">)</span><span class="token operator">:</span> pointer <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 77<span class="line"> <span class="token keyword">if</span> heapBumpPtr <span class="token operator">+</span> size<span class="token operator">.</span>int <span class="token operator">></span> heapMaxPtr<span class="token operator">:</span></span> 78<span class="line"> <span class="token keyword">return</span> <span class="token keyword">nil</span></span> 79<span class="line"></span> 80<span class="line"> result <span class="token operator">=</span> <span class="token function">cast[pointer]</span><span class="token punctuation">(</span>heapBumpPtr<span class="token punctuation">)</span></span> 81<span class="line"> inc heapBumpPtr<span class="token punctuation">,</span> size<span class="token operator">.</span>int</span> 82<span class="line"></span> 83<span class="line"><span class="token keyword">proc</span> <span class="token function">calloc<span class="token operator">*</span></span><span class="token punctuation">(</span>num<span class="token operator">:</span> csize_t<span class="token punctuation">,</span> size<span class="token operator">:</span> csize_t<span class="token punctuation">)</span><span class="token operator">:</span> pointer <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 84<span class="line"> result <span class="token operator">=</span> <span class="token function">malloc</span><span class="token punctuation">(</span>
84size <span class="token operator">*</span> num<span class="token punctuation">)</span></span> 85<span class="line"></span> 86<span class="line"><span class="token keyword">proc</span> <span class="token function">realloc<span class="token operator">*</span></span><span class="token punctuation">(</span>p<span class="token operator">:</span> pointer<span class="token punctuation">,</span> new_size<span class="token operator">:</span> csize_t<span class="token punctuation">)</span><span class="token operator">:</span> pointer <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 87<span class="line"> result <span class="token operator">=</span> <span class="token function">malloc</span><span class="token punctuation">(</span>new_size<span class="token punctuation">)</span></span> 88<span class="line"> <span class="token function">copyMem</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> p<span class="token punctuation">,</span> new_size<span class="token punctuation">)</span></span> 89<span class="line"> <span class="token function">free</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span></span> 90<span class="line"></span> 91<span class="line"><span class="token keyword">proc</span> <span class="token function">free<span class="token operator">*</span></span><span class="token punctuation">(</span>p<span class="token operator">:</span> pointer<span class="token punctuation">)</span> <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 92<span class="line"> <span class="token keyword">discard</span></span> 93<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Notice that I added the <code>{.used.}</code> pragma at the top of the file. This tells the compiler to consider the module as used, even if we don't call any of its procs directly. Otherwise, the compiler will consider it dead code and will eliminate it from the output.</p><p>For Nim to actually know about this module, we need to import it in our main module:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># main.nim</span></span> 94<span class="line"></span> 95<span class="line"><span class="token keyword">import</span> malloc</span> 96<span class="line"><span class="token operator">...</span></span> 97<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Now let's pass the <code>-d:useMalloc</code> flag to the compiler and try to compile again:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c <span class="token punctuation">\\</span></span> 98<span class="line"> <span class="token parameter variable">--nimcache:build</span> <span class="token punctuation">\\</span></span> 99<span class="line"> <span class="token parameter variable">--cpu:amd64</span> <span class="token punctuation">\\</span></span> 100<span class="line"> <span class="token parameter variable">--os:any</span> <span class="token punctuation">\\</span></span> 101<span class="line"> <span class="token parameter variable">--cc:clang</span> <span class="token punctuation">\\</span></span> 102<span class="line"> --passc:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 103<span class="line"> --passc:<span class="token string">"-ffreestanding"</span> <span class="token punctuation">\\</span></span> 104<span class="line"> --passl:<span class="token string">"-fuse-ld=lld-link"</span> <span class="token punctuation">\\</span></span> 105<span class="line"> --passl:<span class="token string">"-nostdlib"</span> <span class="token punctuation">\\</span></span> 106<span class="line"> --passl:<span class="token string">"-Wl,-entry:main"</span> <span class="token punctuation">\\</span></span> 107<span class="line"> --passl:<span class="token string">"-Wl,-subsystem:efi_application"</span> <span class="token punctuation">\\</span></span> 108<span class="line highlighted"> <span class="token parameter variable">-d:useMalloc</span> <span class="token punctuation">\\</span></span> 109<span class="line"> --out:build/main.exe <span class="token punctuation">\\</span></span> 110<span class="line"> main.nim</span></span></span> 111<span class="line"><span class="token output">...</span> 112<span class="line">/home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@slib@sstd@[email protected]:8:10: fatal error: 'string.h' file not found</span> 113<span class="line"> 8 | #include <string.h></span> 114<span class="line"> | ^~~~~~~~~~</span> 115<span class="line">/home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:8:10: fatal error: 'setjmp.h' file not found</span> 116<span class="line"> 8 | #include <setjmp.h></span> 117<span class="line"> | ^~~~~~~~~~</span> 118<span class="line">/home/khaled/.cache/nim/main_d/@mmain.nim.c:113:5: error: conflicting types for 'main'</span> 119<span class="line"> 113 | int main(int argc, char** args, char** env) {</span> 120<span class="line"> | ^</span> 121<span class="line">/home/khaled/.cache/nim/main_d/@mmain.nim.c:65:29: note: previous definition is here</span> 122<span class="line"> 65 | N_LIB_PRIVATE N_NIMCALL(NI, main)(void) {</span> 123<span class="line"> |</span> 124<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>We're getting a different error, which means that Nim is happy with our memory allocation primitives.</p><p>At first glance, it looks like we're missing some C headers. It turns out that <code>clang</code> needs to be told where to find the system headers. In my case, the headers are located in <code>/usr/include</code> (on macOS, the system headers are located at <code>
124\`xcrun --show-sdk-path\`/usr/include</code>), so we'll pass that to the compiler using the <code>-I</code> flag:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c <span class="token punctuation">\\</span></span> 125<span class="line"> <span class="token parameter variable">--nimcache:build</span> <span class="token punctuation">\\</span></span> 126<span class="line"> <span class="token parameter variable">--cpu:amd64</span> <span class="token punctuation">\\</span></span> 127<span class="line"> <span class="token parameter variable">--os:any</span> <span class="token punctuation">\\</span></span> 128<span class="line"> <span class="token parameter variable">--cc:clang</span> <span class="token punctuation">\\</span></span> 129<span class="line"> --passc:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 130<span class="line"> --passc:<span class="token string">"-ffreestanding"</span> <span class="token punctuation">\\</span></span> 131<span class="line highlighted"> --passc:<span class="token string">"-I/usr/include"</span> <span class="token punctuation">\\</span></span> 132<span class="line"> --passl:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 133<span class="line"> --passl:<span class="token string">"-fuse-ld=lld-link"</span> <span class="token punctuation">\\</span></span> 134<span class="line"> --passl:<span class="token string">"-nostdlib"</span> <span class="token punctuation">\\</span></span> 135<span class="line"> --passl:<span class="token string">"-Wl,-entry:main"</span> <span class="token punctuation">\\</span></span> 136<span class="line"> --passl:<span class="token string">"-Wl,-subsystem:efi_application"</span> <span class="token punctuation">\\</span></span> 137<span class="line"> <span class="token parameter variable">-d:useMalloc</span> <span class="token punctuation">\\</span></span> 138<span class="line"> --out:build/main.exe <span class="token punctuation">\\</span></span> 139<span class="line"> main.nim</span></span></span> 140<span class="line"><span class="token output">...</span> 141<span class="line">/home/khaled/.cache/nim/main_d/@mmain.nim.c:113:5: error: conflicting types for 'main'</span> 142<span class="line"> 113 | int main(int argc, char** args, char** env) {</span> 143<span class="line"> | ^</span> 144<span class="line">/home/khaled/.cache/nim/main_d/@mmain.nim.c:65:29: note: previous definition is here</span> 145<span class="line"> 65 | N_LIB_PRIVATE N_NIMCALL(NI, main)(void) {</span> 146<span class="line"> |</span> 147<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote><p><strong>Note</strong>: On macOS, the system headers are located at <code>\`xcrun --show-sdk-path\`/usr/include</code>, so you'll need to replace <code>/usr/include</code> with that path in the <code>--passc</code> flag. Also, you'll need to pass <code>--passc:"-fgnuc-version=4.2.1"</code> (which defines <code>__GNUC__</code>) to avoid any macOS-specific marcros and stick with the GNU C ones.</p></blockquote><p>In order to understand what's going on here it's important to note that, unlike C, Nim programs are not required to have a <code>main</code> function. You can have a file with code at the top level and it will be executed when the program starts. When we defined a <code>main</code> proc ( which, to Nim, is just another proc that has no special meaning), we caused a conflict with the <code>main</code> function that the Nim compiler generates by default. Since we're not going to rely on the C library startup code, we need to take over the startup process ourselves. We can tell Nim to not generate its own <code>main</code> function by passing the <code>--noMain:on</code> flag.</p><p>However, by doing so, we lose initialization of global variables done by the automatically generated <code>NimMain</code> function. We can get it back by forward importing <code>NimMain</code> and calling it from our <code>main</code> proc:</p><div class="language-nim line-numbers-mode" data-highlighter="prismjs" data-ext="nim" data-title="nim"><pre><code><span class="line"><span class="token comment"># main.nim</span></span> 148<span class="line"></span> 149<span class="line"><span class="token keyword">proc</span> <span class="token function">NimMain</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{.</span>importc<span class="token punctuation">.}</span></span> 150<span class="line"></span> 151<span class="line"><span class="token keyword">proc</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> int <span class="token punctuation">{.</span>exportc<span class="token punctuation">.}</span> <span class="token operator">=</span></span> 152<span class="line"> <span class="token function">NimMain</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 153<span class="line"> <span class="token keyword">return</span> <span class="token number">0</span></span> 154<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Let's try to compile again with the <code>--noMain:on</code> flag:</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c <span class="token punctuation">\\</span></span> 155<span class="line"> <span class="token parameter variable">--nimcache:build</span> <span class="token punctuation">\\</span></span> 156<span class="line"> <span class="token parameter variable">--cpu:amd64</span> <span class="token punctuation">\\</span></span> 157<span class="line"> <span class="token parameter variable">--os:any</span> <span class="token punctuation">\\</span></span> 158<span class="line"> <span class="token parameter variable">--cc:clang</span> <span class="token punctuation">\\</span></span> 159<span class="line"> --passc:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 160<span class="line"> --passc:<span class="token string">"-ffreestanding"</span> <span class="token punctuation">\\</span></span> 161<span class="line"> --passc:<span class="token string">"-I/usr/include"</span> <span class="token punctuation">\\</span></span> 162<span class="line"> --passl:<span class="token string">"-target x86_64-unknown-windows"</span> <span class="token punctuation">\\</span></span> 163<span class="line"> --passl:<span class="token string">"-fuse-ld=lld-link"</span> <span class="token punctuation">\\</span></span> 164<span class="line"> --passl:<span class="token string">"-nostdlib"</span> <span class="token punctuation">\\</span></span> 165<span class="line"> --passl:<span class="token string">"-Wl,-entry:main"</span> <span class="token punctuation">\\</span></span> 166<span class="line"> --passl:<span class="token string">"-Wl,-subsystem:efi_application"</span> <span class="token punctuation">\\</span></span> 167<span class="line"> <span class="token parameter variable">-d:useMalloc</span> <span class="token punctuation">\\</span></span> 168<span class="line highlighted"> <span class="token parameter variable">--noMain:on</span> <span class="token punctuation">\\</span></span> 169<span class="line"> --out:build/main.exe <span class="token punctuation">\\</span></span> 170<span class="line"> main.nim</span></span></span> 171<span class="line"><span class="token output">...</span> 172<span class="line">lld-link: error: undefined symbol: memcpy</span> 173<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@slib@sstd@[email protected]:(nimCopyMem)</span> 174<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(nimCopyMem)</span> 175<span class="line"></span> 176<span class="line">lld-link: error: undefined symbol: stderr</span> 177<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(raiseOutOfMem__system_u5532)</span> 178<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(writeToStdErr__system_u3828)</span> 179<span class="line"></span> 180<span class="line">lld-link: error: undefined symbol: exit</span> 181<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(raiseOutOfMem__system_u5532)</span> 182<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(callDepthLimitReached__system_u4467)</span> 183<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(signalHandler)</span> 184<span class="line">>>> referenced 1 more times</span> 185<span class="line"></span> 186<span class="line">lld-link: error: undefined symbol: fwrite</span> 187<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(rawWrite)</span> 188<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(rawWriteString)</span> 189<span class="line"></span> 190<span class="line">lld-link: error: undefined symbol: fflush</span> 191<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(rawWrite)</span> 192<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(rawWriteString)</span> 193<span class="line"></span> 194<span class="line">lld-link: error: undefined symbol: strlen</span> 195<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(nimCStrLen)</span> 196<span class="line"></span> 197<span class="line">lld-link: error: undefined symbol: signal</span> 198<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(registerSignalHandler__system_u4487)</span> 199<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(registerSignalHandler__system_u4487)</span> 200<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(registerSignalHandler__system_u4487)</span> 201<span class="line">>>> referenced 2 more times</span> 202<span class="line"></span> 203<span class="line">lld-link: error: undefined symbol: memset</span> 204<span class="line">>>> referenced by /home/khaled/.cache/nim/main_d/@m..@[email protected]@[email protected]@[email protected]:(nimSetMem__systemZmemory_u7)</span> 205<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>OK, the linker is complaining that it can't find some C functions. This is because we're targeting <code>--os:any</code>, which expects a handful of ANSI C library functions to be available for Nim to use:</p><ul><li><code>memset</code> and <code>memcpy</code> for some memory operations</li><li><code>strlen</code> for string length</li><li><code>fwrite</code> and <code>fflush</code> for writing to a file descriptor</li><li><code>stderr</code> for printing to standard error (not a function, but a global variable)</li><li><code>signal</code> for signal handlers</li><li><code>exit</code> for exiting the program</li></ul><p>Since our OS won't be a POSIX system, we can disable signals by passing the <code>-d:noSignalHandler</code> flag. For the rest of the functions, we'll need to implement them ourselves. Also, Nim includes implementation of some memory functions, which we can leverage by passing the <code>-d:nimNoLibc</code> flag.</p><p>Before we go any further, let's move the compiler flags to a <strong>nim.cfg</strong> file in the project root, so we don't have to pass them every time we compile:</p><div class="language-properties line-numbers-mode" data-highlighter="prismjs" data-ext="properties" data-title="properties"><pre><code><span class="line"><span class="token comment"># nim.cfg</span></span> 206<span class="line"><span class="token key attr-name">
206--nimcache</span><span class="token punctuation">:</span><span class="token value attr-value">build</span></span> 207<span class="line"><span class="token key attr-name">--noMain</span><span class="token punctuation">:</span><span class="token value attr-value">on</span></span> 208<span class="line"><span class="token key attr-name">-d</span><span class="token punctuation">:</span><span class="token value attr-value">useMalloc</span></span> 209<span class="line"><span class="token key attr-name">-d</span><span class="token punctuation">:</span><span class="token value attr-value">nimNoLibc</span></span> 210<span class="line"><span class="token key attr-name">-d</span><span class="token punctuation">:</span><span class="token value attr-value">noSignalHandler</span></span> 211<span class="line"><span class="token key attr-name">--cpu</span><span class="token punctuation">:</span><span class="token value attr-value">amd64</span></span> 212<span class="line"><span class="token key attr-name">--os</span><span class="token punctuation">:</span><span class="token value attr-value">any</span></span> 213<span class="line"><span class="token key attr-name">--cc</span><span class="token punctuation">:</span><span class="token value attr-value">clang</span></span> 214<span class="line"><span class="token key attr-name">--passc</span><span class="token punctuation">:</span><span class="token value attr-value">"-target x86_64-unknown-windows"</span></span> 215<span class="line"><span class="token key attr-name">--passc</span><span class="token punctuation">:</span><span class="token value attr-value">"-ffreestanding"</span></span> 216<span class="line"><span class="token key attr-name">--passc</span><span class="token punctuation">:</span><span class="token value attr-value">"-I/usr/include"</span></span> 217<span class="line"><span class="token key attr-name">--passl</span><span class="token punctuation">:</span><span class="token value attr-value">"-target x86_64-unknown-windows"</span></span> 218<span class="line"><span class="token key attr-name">--passl</span><span class="token punctuation">:</span><span class="token value attr-value">"-fuse-ld=lld-link"</span></span> 219<span class="line"><span class="token key attr-name">--passl</span><span class="token punctuation">:</span><span class="token value attr-value">"-nostdlib"</span></span> 220<span class="line"><span class="token key attr-name">--passl</span><span class="token punctuation">:</span><span class="token value attr-value">"-Wl,-entry:main"</span></span> 221<span class="line"><span class="token key attr-name">--passl</span><span class="token punctuation">:</span><span class="token value attr-value">"-Wl,-subsystem:efi_application"</span></span> 222<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c main.nim --out:build/main.exe</span></span></span> 223<span class="line"><span class="token output">.../lib/std/typedthreads.nim(51, 10) Error: Threads not implemented for os:any. Please compile with --threads:off.</span> 224<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div></div></div><p>This seems weird. The <code>--os:any</code> target should disable threads by default, which we know is true because we didn't get this error when we passed the flags on the command line. It turns out that Nim processes its default <code>nim.cfg</code> file (which turns off threads for <code>os:any</code>) <em>before</em> the project <code>nim.cfg</code> file (which defines <code>--os:any</code>). So by the time the project <code>nim.cfg</code> file is processed, threads are already enabled. We can technically disable threads in the project <code>nim.cfg</code> file using <code>--threads:off</code>, but since the default <code>nim.cfg</code> makes a lot of decisions based on the <code>os</code> flag, we'll need to pass this flag explicitly every time we compile.</p><div class="language-sh-session line-numbers-mode" data-highlighter="prismjs" data-ext="sh-session" data-title="sh-session"><pre><code><span class="line"><span class="token command"><span class="token shell-symbol important">$</span> <span class="token bash language-bash">nim c <span class="token parameter variable">--os:any</span> main.nim --out:build/main.exe</span></span></span> 225<span class="line"><span class="token output">...</span> 226<span class="line">lld-link: error: undefined symbol: stderr</span> 227<span class="line">lld-link: error: undefined symbol: exit</span> 228<span class="line">lld-link: error: undefined symbol: fwrite</span> 229<span class="line">lld-link: error: undefined symbol: fflush</span> 230<span class="line">...</span> 231<span class="line"></span></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>We get less linker errors now, thanks to the <code>--d:nimNoLibc</code> and <code>--d:noSignalHandler</code> flags. We still, however, need to implement <code>stderr</code>, <code>fwrite</code>, <code>fflush</code>, and <code>exit</code>.</p><p>This section is already too long, so we'll continue in the next section, where we'll implement the missing C functions.</p>`,55)]))}const c=n(l,[["render",t],["__file","03-targeting-uefi-p1.html.vue"]]),r=JSON.parse(`{"path":"/osdev/03-targeting-uefi-p1.html","title":"Targeting UEFI (Part 1)","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Building a PE32+ executable","slug":"building-a-pe32-executable","link":"#building-a-pe32-executable","children":[]},{"level":2,"title":"Cross-compiling Nim to PE32+","slug":"cross-compiling-nim-to-pe32","link":"#cross-compiling-nim-to-pe32","children":[]}
231],"git":{"updatedTime":1744638230000},"filePathRelative":"osdev/03-targeting-uefi-p1.md","excerpt":"\\n<p>Traditionally, booting an operating system on x86/x86_64 hardware has been done using the\\nBIOS. The BIOS has been considered legacy for a long time, and has been replaced by UEFI\\n(Unified Extensible Firmware Interface) on most modern hardware. We no longer have to\\nwrite a boot sector in assembly and rely on BIOS interrupts to load the OS. In this\\nsection we will focus on cross-compiling to UEFI (we'll get to the actual booting part\\nlater).</p>"}`);export{c as comp,r as data};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.