<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged x86</title>
<link>https://coderlegion.com/tag/x86</link>
<description>Powered by Question2Answer</description>
<item>
<title>Notes on x86 PVH boot</title>
<link>https://coderlegion.com/28620/notes-on-x86-pvh-boot</link>
<description>&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;My Rust KVM project now being able to boot Linux on ARM64, I decided to try and&lt;br&gt;
boot Linux on x86. I have been trying in the past to set up the boot procedure&lt;br&gt;
via &lt;code&gt;boot_params&lt;/code&gt; struct. At the time it was a lot more complex and now I wanted&lt;br&gt;
to have some result as soon as possible. So I turned to the PVH&lt;br&gt;
(Para-Virtualized Hardware) Linux boot which simplifies this process a lot. As&lt;br&gt;
a Xen Project protocol, PVH provides a relatively simple interface between a&lt;br&gt;
VMM and the Linux kernel, without requiring the VMM to reproduce the&lt;br&gt;
traditional firmware/bootloader path.&lt;/p&gt;
&lt;h3 id=&quot;entering-protected-mode-via-bios&quot;&gt;Entering protected mode via BIOS&lt;/h3&gt;
&lt;p&gt;The traditional way of booting an operating system on an x86 machine is to start&lt;br&gt;
in real mode and then proceed to protected mode. From there, if CPU is 64-bit,&lt;br&gt;
we can go to long mode. In the PVH Linux entry path, Linux performs the&lt;br&gt;
transition from the required 32-bit protected-mode entry state into 64-bit mode&lt;br&gt;
itself.&lt;/p&gt;
&lt;h4 id=&quot;setting-up-real-mode&quot;&gt;Setting up real mode&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;x86 starts at reset vector (physical address &lt;code&gt;0xFFFFFFF0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CPU is in 16-bit real mode and firmware like SeaBIOS takes over&lt;/li&gt;
&lt;li&gt;SeaBIOS looks at first sector (512 bytes) of a bootable device&lt;/li&gt;
&lt;li&gt;if the sector contains &lt;code&gt;0xAA55&lt;/code&gt; at 510-byte offset, it's loaded at &lt;code&gt;0x7C00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;load the rest of data manually from our boot device (e.g. on &lt;code&gt;0x8000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;jump to the code section we just loaded (e.g. to address &lt;code&gt;0x8000&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;if necessary, set up Stack Pointer and Stack Segment&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;getting-to-protected-mode&quot;&gt;Getting to protected mode&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;make sure interrupts are disabled&lt;/li&gt;
&lt;li&gt;set up and load a minimal GDT (Global Descriptor Table)&lt;/li&gt;
&lt;li&gt;set PE (Protected Mode Enable) bit in CR0 (Control Register)&lt;/li&gt;
&lt;li&gt;perform far jump selecting a proper GDT entry&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;gdt-example&quot;&gt;GDT example&lt;/h4&gt;
&lt;p&gt;A minimal GDT code set up looks like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;gdt_start:
dq 0
gdt_code:; index 1
dw 0xFFFF; segment limit (low)
dw 0x0000; segment base (low)
db 0x00; base address (middle)
db 10011010b; access bits (read/write, executable, code or data, present)
db 11001111b; flags (32-bit protected, 4k blocks) - segment limit (high)
db 0x00; base address (high)

gdt_data:; index 2
dw 0xFFFF; segment limit (low)
dw 0x0000; segment base (low)
db 0x00; base address (middle)
db 10010010b; access bits (read/write, code or data, present)
db 11001111b; flags (32-bit protected, 4k blocks) - segment limit (high)
db 0x00; base address (high)
gdt_end:

gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we enter protected mode by executing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;cli
lgdt [gdt_descriptor]

mov eax, cr0
or eax, 1
mov cr0, eax

jmp 0x08:protected_mode_start ; 0x08 is 1 &amp;lt;&amp;lt; 3 (select index 1 in GDT)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The far jump reloads &lt;code&gt;CS&lt;/code&gt; using the descriptor selected by &lt;code&gt;0x08&lt;/code&gt;. The other&lt;br&gt;
segment registers must be loaded with appropriate protected-mode selectors as&lt;br&gt;
well. Each segment register has a visible selector and hidden descriptor-cache&lt;br&gt;
state containing information such as the base, limit and access rights.&lt;/p&gt;
&lt;h3 id=&quot;how-pvh-helps&quot;&gt;How PVH helps&lt;/h3&gt;
&lt;p&gt;If we are in a virtual machine, we don't really need to go through all of these&lt;br&gt;
steps. We don't need to have firmware or BIOS to set up CPU state properly, we&lt;br&gt;
can simply do this from the VM.&lt;/p&gt;
&lt;p&gt;Here, we can manipulate the vCPU to our will, and therefore we can set segment&lt;br&gt;
registers ourselves to valid values, skipping GDT setup. We do need to do some&lt;br&gt;
administration to help Linux understand what's going on and this is exactly&lt;br&gt;
where PVH comes in.&lt;/p&gt;
&lt;h4 id=&quot;elf-pvh-note&quot;&gt;ELF PVH note&lt;/h4&gt;
&lt;p&gt;To determine whether an x86 Linux ELF image supports the PVH boot ABI, we can&lt;br&gt;
inspect its Xen ELF notes and look for &lt;code&gt;XEN_ELFNOTE_PHYS32_ENTRY&lt;/code&gt; (note type&lt;br&gt;
&lt;code&gt;18&lt;/code&gt;). Its descriptor gives the physical 32-bit entry point at which the guest&lt;br&gt;
should be started.&lt;/p&gt;
&lt;p&gt;Newer kernels may also contain &lt;code&gt;XEN_ELFNOTE_PHYS32_RELOC&lt;/code&gt; (type &lt;code&gt;19&lt;/code&gt;), which&lt;br&gt;
describes alignment and physical-address constraints for relocatable PVH&lt;br&gt;
kernels.&lt;/p&gt;
&lt;h3 id=&quot;pvh-boot-memory-setup&quot;&gt;PVH boot memory setup&lt;/h3&gt;
&lt;p&gt;Boot procedure from PVH is more straightforward, but requires more memory&lt;br&gt;
management from VM:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;get RIP from ELF PVH note entry&lt;/li&gt;
&lt;li&gt;load Linux kernel ELF segments into memory&lt;/li&gt;
&lt;li&gt;copy kernel command line to memory&lt;/li&gt;
&lt;li&gt;construct an E820-like memory map using &lt;code&gt;HvmMemEntry&lt;/code&gt; entries&lt;/li&gt;
&lt;li&gt;construct and fill out &lt;code&gt;HvmStartInfo&lt;/code&gt; struct&lt;/li&gt;
&lt;li&gt;load memory entries and &lt;code&gt;HvmStartInfo&lt;/code&gt; to memory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If using &lt;code&gt;initramfs&lt;/code&gt; you also need to load it into memory, along with its&lt;br&gt;
separate &lt;code&gt;initramfs&lt;/code&gt; command line. We also need to fill out &lt;code&gt;HvmModuleEntry&lt;/code&gt;&lt;br&gt;
struct to point to &lt;code&gt;initramfs&lt;/code&gt; and its command line address. Note that&lt;br&gt;
here we are not talking about kernel modules, but rather Xen modules, and&lt;br&gt;
for Linux that only means &lt;code&gt;initramfs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9777136250809845640&quot; alt=&quot;ELF with PVH&quot;&gt;&lt;/p&gt;
&lt;p&gt;A good reference for the structures above is actually in the Linux kernel&lt;br&gt;
headers and can be found on:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v7.1.3/source/include/xen/interface/hvm/start_info.h&quot;&gt;https://elixir.bootlin.com/linux/v7.1.3/source/include/xen/interface/hvm/start_info.h&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;setting-up-registers&quot;&gt;Setting up registers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RIP points to entrypoint from ELF note &lt;code&gt;18&lt;/code&gt; entry&lt;/li&gt;
&lt;li&gt;RBX points to &lt;code&gt;HvmStartInfo&lt;/code&gt; physical address (this is PVH specific)&lt;/li&gt;
&lt;li&gt;set up segment registers to valid Protected Mode state&lt;/li&gt;
&lt;li&gt;enable PE bit in CR0 (as in ordinary boot)&lt;/li&gt;
&lt;li&gt;initialize CR4 (Control Register 4) to zero&lt;/li&gt;
&lt;li&gt;clear VM (bit 17), IF (bit 9) and TF (bit 8) in RFLAGS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linux kernel-side PVH register setup is described here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v7.1.3/source/arch/x86/platform/pvh/head.S#L30&quot;&gt;https://elixir.bootlin.com/linux/v7.1.3/source/arch/x86/platform/pvh/head.S#L30&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;segment-registers&quot;&gt;Segment registers&lt;/h4&gt;
&lt;p&gt;I think the best source for how to set up segment registers is actually my own&lt;br&gt;
code from my Rust KVM project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let code = kvm_segment {
base: 0,
limit: 0xffff_ffff,
selector: 1 &amp;lt;&amp;lt; 3,
type_: 0b1010, // 3rd bit: code/data = 1
   // 2nd bit: conforming = 0
   // 1st bit: readable = 1
   // 0th bit: accessed = 0
present: 1,
dpl: 0, // descriptor privilege level (0: kernel .. 3: userspace)
db: 1,  // default operand / address size (0: 16bit, 1: 32bit)
s: 1,   // system (1: code/data segment, 0: system segment)
l: 0,   // long mode (0: not 64bit segment, 1: 64bit segment)
g: 1,   // granularity (0: limit in bytes, 1: limit in 4Kb blocks)
avl: 0, // available for software (reserved for custom OS purposes)
unusable: 0,
padding: 0,
};

let data = kvm_segment {
selector: 2 &amp;lt;&amp;lt; 3,
type_: 0b0010, // 3rd bit: code/data = 0
   // 2nd bit: conforming = 0
   // 1st bit: writable = 1
   // 0th bit: accessed = 0
..code };

sregs2.tr = kvm_segment {
base: 0,
limit: 0x67, // as per Xen project PVH protocol
g: 0,
..code };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With BIOS boot, these segment descriptors would live in a GDT in guest memory&lt;br&gt;
and the CPU would obtain their hidden descriptor state by loading the&lt;br&gt;
corresponding selectors. With KVM, we can bypass the GDT entirely and directly&lt;br&gt;
initialize the vCPU's segment state through &lt;code&gt;KVM_SET_SREGS&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;PVH turned out to be a convenient way of getting my x86 KVM implementation to&lt;br&gt;
the point where it could boot a real Linux kernel without first implementing&lt;br&gt;
the traditional x86 boot protocol.&lt;/p&gt;
&lt;p&gt;There is still plenty left to implement around the x86 side of the VMM, but&lt;br&gt;
being able to reach the Linux kernel through PVH gives me a much more useful&lt;br&gt;
starting point than reproducing the complete BIOS boot sequence first.&lt;/p&gt;
&lt;p&gt;The implementation is available in the x86 initialization code in my &lt;code&gt;kvm-rust&lt;/code&gt;&lt;br&gt;
repository:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/blob/main/src/arch/x86_64/init.rs&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/blob/main/src/arch/x86_64/init.rs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I plan to continue extending functionality of the x86 side of my VMM.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28620/notes-on-x86-pvh-boot</guid>
<pubDate>Fri, 25 Sep 2026 14:47:35 +0000</pubDate>
</item>
<item>
<title>Automating Stack Corruption Analysis in GDB with Python</title>
<link>https://coderlegion.com/20314/automating-stack-corruption-analysis-in-gdb-with-python</link>
<description>&lt;h3 id=&quot;a-bug-in-my-operating-system&quot;&gt;A bug in my operating system&lt;/h3&gt;
&lt;p&gt;During a recent visit to my wife's family in Sarajevo, I decided to revisit my&lt;br&gt;
hobby operating system in QEMU. I discovered that the boot process consistently&lt;br&gt;
froze while printing the BIOS memory map. What initially looked like a&lt;br&gt;
protected-mode issue eventually turned into a useful exercise in automating&lt;br&gt;
debugging using GDB's Python scripting.&lt;/p&gt;
&lt;h3 id=&quot;manual-debugging-failure&quot;&gt;Manual debugging failure&lt;/h3&gt;
&lt;p&gt;After reinspecting some common pitfalls in my protected mode setup I was more&lt;br&gt;
confident that the issue was stack corruption in my &lt;code&gt;print_memory_map&lt;/code&gt; function.&lt;br&gt;
If I had an unmatched push or pop, it could have corrupted return addresses and&lt;br&gt;
eventually redirected execution flow into invalid memory. Single-stepping&lt;br&gt;
through the routine manually quickly became impractical. The function mixed BIOS&lt;br&gt;
interrupt handling, memory map parsing and multiple helper calls, making it&lt;br&gt;
difficult to reason about stack state over time.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-gdb-scripting-in-python&quot;&gt;Setting up GDB scripting in Python&lt;/h3&gt;
&lt;p&gt;I needed to automate this and GDB's integration with Python was the most&lt;br&gt;
promising route I could take. The idea was to do exactly what I started&lt;br&gt;
manually: break at a specific (suspicious) function and then start single&lt;br&gt;
stepping while inspecting how the stack pointer behaved.&lt;/p&gt;
&lt;p&gt;First things first, we import GDB module in Python, connect to the remote&lt;br&gt;
target and set up a breakpoint (this is done by inheriting from&lt;br&gt;
&lt;code&gt;gdb.Breakpoint&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;import gdb

class StackTraceBreakpoint(gdb.Breakpoint):

    def __init__(self, func_name):
        super(StackTraceBreakpoint, self).__init__(func_name)
        self.active = False
    
    def stop(self):
        if self.active:
            raise Exception(&quot;Recursion detected - stopping.&quot;)
        self.active = True

        return True

if __name__ == &quot;__main__&quot;:
    gdb.execute(&quot;target remote :5555&quot;)
    gdb.execute(&quot;symbol-file ../build/arch/x86/bios-legacy/boot-stage1-5.elf&quot;)

    tracer = StackTraceBreakpoint(&quot;print_memory_map&quot;)
    gdb.execute(&quot;continue&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;stacktracebreakpoint&quot;&gt;StackTraceBreakpoint&lt;/h4&gt;
&lt;p&gt;So this is kind of bare-bones of what I wanted to do. This code will simply add&lt;br&gt;
a breakpoint with custom logic after connecting to QEMU and loading symbols.&lt;br&gt;
As soon as we do &lt;code&gt;gdb.execute(&quot;continue&quot;)&lt;/code&gt; GDB will run and, if and when it hits&lt;br&gt;
our breakpoint, it will execute whatever we wrote in the &lt;code&gt;stop()&lt;/code&gt; method. For&lt;br&gt;
now I only added a kind of assertion that we cannot analyze recursions (I didn't&lt;br&gt;
use any in my code anyway and logic would be a bit more complex).&lt;/p&gt;
&lt;h4 id=&quot;single-stepping&quot;&gt;Single-stepping&lt;/h4&gt;
&lt;p&gt;Now what we need to do is start single-stepping after we hit our breakpoint. So&lt;br&gt;
we add a while loop with &lt;code&gt;gdb.execute(&quot;stepi&quot;)&lt;/code&gt; after &lt;code&gt;gdb.execute(&quot;continue&quot;)&lt;/code&gt;.&lt;br&gt;
Note that the &lt;code&gt;stepi&lt;/code&gt; instruction steps over machine instructions, not over&lt;br&gt;
source code statements. Also note that we cannot start single-stepping in&lt;br&gt;
&lt;code&gt;stop()&lt;/code&gt; method because GDB won't be in a state which can accept these kinds of&lt;br&gt;
debugging requests.&lt;/p&gt;
&lt;h3 id=&quot;detecting-stack-imbalance&quot;&gt;Detecting stack imbalance&lt;/h3&gt;
&lt;p&gt;Furthermore I have wrapped this single-stepping logic in &lt;code&gt;trace_step()&lt;/code&gt; method&lt;br&gt;
in our breakpoint class. This method is not part of GDB breakpoint API, but&lt;br&gt;
rather just a convenience for tracking the number of pushes and pops in a&lt;br&gt;
consistent manner. To run this script we need to call:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;gdb -ex 'source debug-stack.py'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another thing we need to track is whether we have entered another function (in&lt;br&gt;
which case we won't be counting pushes and pops) and if we have returned from&lt;br&gt;
it. If I ever wanted to inspect routines being called, I would just run the same&lt;br&gt;
script for them (my intention here is not creating a custom emulator on top of&lt;br&gt;
GDB). So I added a counter &lt;code&gt;func_count&lt;/code&gt; which will increase on &lt;code&gt;call&lt;/code&gt; and&lt;br&gt;
decrease on &lt;code&gt;ret&lt;/code&gt; instruction. Here is a rough idea:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;def trace_step(self):
insn_full = gdb.execute(&quot;x/i $pc&quot;, to_string=True).strip()
insn = re.search(r'^=&amp;gt;.*:\s*([^\s].*)$', insn_full).group(1).split(&quot; &quot;)[0]

match insn:
case &quot;push&quot;:
if self.func_count == 0:
self.pushes += 1
case &quot;pop&quot;:
if self.func_count == 0:
self.pops += 1
case &quot;call&quot;:
self.func_count += 1
case &quot;ret&quot;:
self.func_count -= 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you can see how I'm extracting the instruction from GDB. And what is left&lt;br&gt;
is just improving logic and also fetching registers like PC, SP and CS for&lt;br&gt;
debugging. I log everything into a file as GDB can get really noisy with&lt;br&gt;
standard output (I didn't find a way to turn off all logging in GDB completely&lt;br&gt;
when single stepping). The script is available in my GitHub repository:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/raspios/tree/master/scripts/debug-stack.py&quot;&gt;https://github.com/StjepanPoljak/raspios/tree/master/scripts/debug-stack.py&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-the-root-cause&quot;&gt;Finding the root cause&lt;/h3&gt;
&lt;p&gt;Finally, you can see an example output detecting my very issue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;[START] print_memory_map SP=fffd
[0000:8183] push %ax (SP=0xfffd)
[0000:8185] push %bx (SP=0xfff9)
[0000:8187] push %cx (SP=0xfff5)
[0000:8189] push %dx (SP=0xfff1)
[0000:81a5] call 0x66e980a5 (SP=0xffed)
[0000:80a3] push %ax (SP=0xffeb)
[0000:80a7] call 0xab18056 (SP=0xffe7)
(...)
[0000:806e] ret  (SP=0xffef)
[0000:8098] call 0xf6ec8056 (SP=0xfff1)
[0000:8054] push %ax (SP=0xffef)
[0000:8056] push %bx (SP=0xffeb)
[0000:806a] pop %bx (SP=0xffe7)
[0000:806c] pop %ax (SP=0xffeb)
[0000:806e] ret  (SP=0xffef)
[0000:8098] call 0xf6ec8056 (SP=0xfff1)
[0000:8054] push %ax (SP=0xffef)
[0000:8056] push %bx (SP=0xffeb)
[0000:806a] pop %bx (SP=0xffe7)
[0000:806c] pop %ax (SP=0xffeb)
[0000:806e] ret  (SP=0xffef)
[0000:809d] pop %esi (SP=0xfff1)
[0000:809e] pop %bx (SP=0xfff3)
[0000:80a0] pop %ax (SP=0xfff7)
[0000:80a2] ret  (SP=0xfffb)
[FAIL] Extra pop detected at [0000:81fa].
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the real culprit was an extra &lt;code&gt;pop eax&lt;/code&gt; in my &lt;code&gt;print_memory_map&lt;/code&gt; routine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;print_memory_map:
push eax
push ebx
push ecx
push edx

; --- ommited print loop logic ---

.noprint_newline:
pop eax

cmp ecx, [memory_map_size]
jne .print_memory_map_loop

pop edx
pop ecx
pop ebx
pop eax
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing this line will cause my debugging script to successfully pass.&lt;/p&gt;
&lt;h3 id=&quot;try-it-out-yourself&quot;&gt;Try it out yourself&lt;/h3&gt;
&lt;p&gt;You can try it out yourself, just check out my operating system, &lt;code&gt;raspios&lt;/code&gt;, on&lt;br&gt;
GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/raspios&quot;&gt;https://github.com/StjepanPoljak/raspios&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Build and run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir build
cd build
ARCH=x86 cmake ..
make
make qemu_debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in the &lt;code&gt;scripts&lt;/code&gt; folder run &lt;code&gt;gdb -ex 'source debug-stack.py'&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This was a good reminder that low-level debugging often benefits from&lt;br&gt;
lightweight tooling tailored to the problem at hand. In this case, a small&lt;br&gt;
amount of Python automation around GDB made stack corruption analysis&lt;br&gt;
significantly more manageable than manual instruction tracing.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20314/automating-stack-corruption-analysis-in-gdb-with-python</guid>
<pubDate>Thu, 11 Jun 2026 15:55:57 +0000</pubDate>
</item>
<item>
<title>Gloria JIT v4.4.0 — Bare-Metal Control, Memory Primitives, and Structured Flow</title>
<link>https://coderlegion.com/19452/gloria-jit-v4-4-0-bare-metal-control-memory-primitives-and-structured-flow</link>
<description>&lt;h3 id=&quot;what-is-gloria-jit-&quot;&gt;What is Gloria JIT?&lt;/h3&gt;
&lt;p&gt;Gloria JIT is a low-level programming language and compiler that is part of the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;ForgeZero&lt;/a&gt; ecosystem. It is written in Go and compiles source code directly to x86-64 machine code — no LLVM, no GCC, no Clang in the middle.&lt;/p&gt;
&lt;p&gt;The goal is straightforward: give the programmer direct, unmediated control over the machine. No intermediate representation handed off to a third-party backend. No optimizer making decisions you didn't ask for. The compiler emits raw bytes, and those bytes run.&lt;/p&gt;
&lt;p&gt;This makes Gloria JIT an interesting project for anyone curious about how compilers actually work, or for developers who want to explore bare-metal programming without the abstraction layers that most toolchains introduce.&lt;/p&gt;
&lt;p&gt;v4.4.0 expands the language significantly, adding structured control flow, direct memory and I/O access, and a bare-metal output path for VGA framebuffer writing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;while-loops-structured-control-flow&quot;&gt;&lt;code&gt;while&lt;/code&gt; Loops — Structured Control Flow&lt;/h3&gt;
&lt;p&gt;Before this release, repeated execution required manual branching. v4.4.0 adds proper &lt;code&gt;while&lt;/code&gt; loops.&lt;/p&gt;
&lt;p&gt;A loop runs as long as its condition is non-zero. Inside the loop body you can use standard assignment and mutation operators (&lt;code&gt;=&lt;/code&gt;, &lt;code&gt;+=&lt;/code&gt;, &lt;code&gt;-=&lt;/code&gt;), and built-in calls are permitted as well. This is a meaningful step toward the kind of control flow you'd expect from a general-purpose language.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;memory-primitives-peek-and-poke&quot;&gt;Memory Primitives — &lt;code&gt;peek&lt;/code&gt; and &lt;code&gt;poke&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Two new built-in functions expose direct memory access:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;peek(address)&lt;/code&gt; — reads a 16-bit value from the given address&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poke(address, value)&lt;/code&gt; — writes a 16-bit value to the given address&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both accept either immediate values or runtime variables as arguments.&lt;/p&gt;
&lt;p&gt;This is the kind of primitive that exists in very few high-level languages, but is essential for bare-metal work — writing to hardware registers, inspecting memory-mapped I/O, or building your own allocator from scratch. Handle with care.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;x86-64-port-i-o&quot;&gt;x86-64 Port I/O&lt;/h3&gt;
&lt;p&gt;For environments that use port-mapped I/O (common in older PC hardware and embedded x86 systems), two new built-ins are available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;in8(port)&lt;/code&gt; — reads a single byte from the given I/O port (zero-extended)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;out8(port, value)&lt;/code&gt; — writes a byte to the given I/O port&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This enables direct hardware interaction at a level that most programming languages simply do not expose.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;vga-framebuffer-output&quot;&gt;VGA Framebuffer Output&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;print(string)&lt;/code&gt; now supports a bare-metal execution path that writes directly to the VGA text buffer at memory address &lt;code&gt;0xB8000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For context: on x86 PCs, this address is where text-mode video memory lives. Writing bytes there places characters directly on screen — no operating system, no drivers, no system calls involved. This is how early PC software (and modern bootloaders) produce output.&lt;/p&gt;
&lt;p&gt;Details of the implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default text color is green (&lt;code&gt;0x0A&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Register &lt;code&gt;R15&lt;/code&gt; is reserved as a cursor offset and is preserved across calls&lt;/li&gt;
&lt;li&gt;Escape sequences &lt;code&gt;\n&lt;/code&gt; and &lt;code&gt;\t&lt;/code&gt; are resolved at compile time into the appropriate control characters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To support testing without real hardware, two utilities are included: &lt;code&gt;patchVGA()&lt;/code&gt; swaps the real framebuffer address for a heap-allocated buffer, and &lt;code&gt;dumpVGA()&lt;/code&gt; renders that buffer to stdout via a direct syscall, with zero heap allocations.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;register-constants&quot;&gt;Register Constants&lt;/h3&gt;
&lt;p&gt;To reduce ambiguity in the backend IR and make generated code easier to reason about, named constants have been introduced for all general-purpose x86-64 registers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;regRAX(0), regRCX(1), regRDX(2), regRBX(3),
regRSP(4), regRBP(5), regRSI(6), regRDI(7),
regR8(8) ... regR15(15)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;internal-changes&quot;&gt;Internal Changes&lt;/h3&gt;
&lt;p&gt;A few backend improvements shipped alongside the language features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emitLowLevelPrint&lt;/code&gt; now accepts a &lt;code&gt;kernelMode&lt;/code&gt; flag to switch between syscall-based output and direct VGA writes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emitPushReg&lt;/code&gt; and &lt;code&gt;emitPopReg&lt;/code&gt; now cover the full register set including R8–R15&lt;/li&gt;
&lt;li&gt;New operations: &lt;code&gt;emitMovMemToReg64&lt;/code&gt;, &lt;code&gt;emitMovRegToMem64&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parseStringLiteral&lt;/code&gt; handles escape sequences at compile time rather than at runtime&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emitBareMetalPrint&lt;/code&gt; introduced for the VGA output path&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-this-is-heading&quot;&gt;Where This Is Heading&lt;/h3&gt;
&lt;p&gt;With v4.4.0, Gloria JIT operates in two modes simultaneously: as a userspace compiler with kernel-aware syscall output, and as a bare-metal code generator capable of running without an operating system underneath it.&lt;/p&gt;
&lt;p&gt;The next focus areas are optimization passes and IR stability. If you're interested in how compilers work from the ground up — or in low-level x86 programming without giving up a proper language — this is worth following.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Gloria JIT is part of the ForgeZero project. Follow along on [AlexVoste](&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19452/gloria-jit-v4-4-0-bare-metal-control-memory-primitives-and-structured-flow</guid>
<pubDate>Mon, 01 Jun 2026 22:29:15 +0000</pubDate>
</item>
</channel>
</rss>