Your Title - bootloader2026-09-25T14:25:39+00:00http://example.com/tag/bootloader.htmlAuthor HeretOSh para x86 - i386+ | Parte VI | Booteando en Diferentes Tipos de Máquinas2026-09-18T17:19:19+00:00http://example.com/tOSh-corriendo-en-maquinas<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p>
<h1 id="partes">Partes</h1>
<p>Esta serie de artículos tiene las siguientes partes:</p>
<ul>
<li><a href="/codeando-un-bootloader">Parte I - Bootloader</a></li>
<li><a href="/modo-protegido">Parte II - Stage 1</a></li>
<li><a href="/saltando-a-c">Parte III - Saltando a C</a></li>
<li><a href="/interrupciones">Parte IV - Interrupciones</a></li>
<li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a></li>
<li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</a> <strong><— Estás acá</strong></li>
</ul>
<p><img src="/assets/images/tOSh/tOSh-running-86box.png" alt="tOSh corriendo en 86Box" class="img-responsive" /><em>tOSh corriendo en 86Box</em></p>
<h1 id="release-202609-de-tosh">Release 2026.09 de tOSh</h1>
<p>Este mes (Septiembre, 2026) logré que el sistema operativo bootee y funcione consistentemente en hardware real y emuladores.
Gracias a amigos por prestarme hardware para probar por todos lados.
Falla la disquetera en máquinas muy viejas, onda XTs y 286 que es esperable, porque salto a
[Modo Protegido] casi al toque, pero la disquetera en algunas 386+ a veces no termina de poder leer
archivos y termina fallando, reportado por el <code class="language-plaintext highlighter-rouge">Kernel</code> cosa que no está muy mal.
En las pruebas, hubo algunas sugerencias que me hicieron donde quizá podría hacer un fallback
al BIOS y hacer un wrapper pero no, creo que no voy a ir tan lejos.</p>
<p>Al menos por ahora.</p>
<p>En cambio cargar <code class="language-plaintext highlighter-rouge">tOSh.img</code> en variopintos emuladores es una tarea muy fácil e inclusive, divertida.
Solamente bajate la imagen de floppy de <a href="/resources/">Resources</a> y montala en
el emulador que más te guste.</p>
<p>Si querés correr <code class="language-plaintext highlighter-rouge">tOSh.img</code> en hardware real, vas a tener que transferir la imagen a un disco rígido
del hardware que uses y usar algún programa como <code class="language-plaintext highlighter-rouge">RAWrite</code> y tener en cuenta que es una imagen de 1.44MB,
para disquettes de 3" 1/2.</p>
<p>También dejé el código fuente de la última versión en <a href="/resources/">Resources</a>.</p>
<p><img src="/assets/images/tOSh/tOSh-ola-ke-ase.jpeg" alt="tOSh saludando a cámara" class="img-responsive" /><em>tOSh saludando a cámara</em></p>
<p>Si alguna vez sigo con este proyecto, no voy a darle más soporte a compus viejas, sino que
voy a saltar directamente al <a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a>, habilitar un modo de video moderno, configurar
varios CPUs y desbloquear todo lo que la arquitectura intel pueda ofrecer y
reescribir todo lo que está incompleto de <code class="language-plaintext highlighter-rouge">tOSh</code>.</p>
<h1 id="referencias">Referencias</h1>
<ul>
<li><a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a></li>
</ul>
tOSh para x86 - i386+ | Parte IV | Interrupciones2023-02-06T07:43:04+00:00http://example.com/interrupciones<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p>
<h1 id="partes">Partes</h1>
<p>Esta serie de artículos tiene las siguientes partes:</p>
<ul>
<li><a href="/codeando-un-bootloader">Parte I - Bootloader</a></li>
<li><a href="/modo-protegido">Parte II - Stage 1</a></li>
<li><a href="/saltando-a-c">Parte III - Saltando a C</a></li>
<li><a href="/interrupciones">Parte IV - Interrupciones</a> <strong><— Estás acá</strong></li>
<li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a></li>
<li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</a></li>
</ul>
<p><img src="/assets/images/tOSh/tOSh-development.jpeg" alt="tOSh Kernel Panic!" class="img-responsive" /><em>tOSh Kernel Panic!</em></p>
<h1 id="interrupciones">Interrupciones</h1>
<p>Que la arquitectura de las PCs de antaño y modernas estén basadas en <a href="https://wiki.osdev.org/X86_Interrupts">Interrupciones</a>, es precisamente por este chip, el <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> de Intel.
Una interrupción es una señal que recibe el CPU que frena temporariamente cualquier cosa que esté computando el CPU y de esta manera poder <strong><em>handlear (o manejar)</em></strong> un evento de mayor prioridad.</p>
<p>Para que la PC pueda interactuar con cualquier dispositivo electrónico suficientemente complejo, alguna interacción en mayor o menor medida con un CPU debe haber. Una de las opciones, era directamente ir preguntándole
a cada dispositivo <strong>"¡Eu!, ¿Qué onda?"</strong>.</p>
<p>Sin embargo al estar constantemente preguntando a los dispositivos conectados si necesitan algo, se pierde tiempo y recursos en cada una de las preguntas. A esta estrategia se la llama (<a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a>).
Una de las soluciones propuestas para evitar hacer <a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a> es simplemente permitir que el CPU trabaje en sus cosas y programas normalmente y esperar por una señal en el momento en que se necesita atender una acción de alguien. Los dos dos tipos de interrupciones de CPU más comunes son por ejemplo:</p>
<ul>
<li><strong>Hardware Interrupts</strong>: Son enviadas asincrónicamente desde dispositivos físicos como mouse, teclado, discos rígidos, que cada tanto necesitan atención. Por ejemplo, apretar una tecla del teclado de una PC dispara una interrupción de hardware e <strong>INTERRUPE TODO</strong> y uno tiene que dejarlo que sea que esté ejecutando sea un <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> o un programa en "Userspace" y debe <code class="language-plaintext highlighter-rouge">atender</code> inmediatamente la interrupción.</li>
<li><strong>Software Interrupts</strong>: Excepciones o Traps, generadas internamente por el procesador o por un programa corriendo debido a un error ( como dividir por cero ) o por un <code class="language-plaintext highlighter-rouge">request</code> del sistema operativo via un <code class="language-plaintext highlighter-rouge">system call</code>.</li>
</ul>
<h2 id="cómo-se-handlea-maneja-una-interrupción">¿Cómo se Handlea (maneja) una Interrupción?</h2>
<p><strong>Diego Silicio</strong> en su "<strong>sextillas interrumpidas</strong>" nos recuerda:</p>
<p>acá le venimo' a canta'<br />
en compás he interrumpido<br />
usted, amigo tupido<br />
pero nunca 'ES' como humano<br />
PICante es el paisano<br />
en su línea se ha unido<br /></p>
<p>Críptico el gaucho nos ha interrumpido, pero después de terminar este artículo desasnante, su canto será atendido por todos.</p>
<p>Cuando ocurre una interrupción, el CPU <strong>debería</strong> salvar el contexto de ejecución en algún lado. En el <code class="language-plaintext highlighter-rouge">stack</code> es un buen lugar.
Entonces: Salvar contexto -> Ejecutar un <code class="language-plaintext highlighter-rouge">Interrupt Service Routine</code> <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a> (vamos muy pronto con esto) -> Recuperar el contexto y seguir laburando.
Antes de siquiera empezar a armar las <strong>rutinas de interrupción</strong>, las cuales vamos a <strong>handlear</strong> solamente un par y no todas por ahora, tengo que explicar aún más cosas, porque en el mundo del <strong>PC</strong> nada es, desafortunadamente, sencillo.</p>
<h2 id="el-pic---programmable-interrupt-controller">El PIC - Programmable Interrupt Controller</h2>
<p>En una PC tradicional el <em>Programmable Interrupt Controller</em> o <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> es el intermediario entro los dispositivos y el procesador.
Los dispositivos no le hablan directamente al procesador.
Estos están conectados al PIC mediante líneas de interrupción llamadas <em>Interrupt Request</em> o <strong>IRQs</strong>.
El PIC recibe esas señales y se encarga de avisarle al CPU que hay alguien pidiendo atención.</p>
<p>Si leemos un toque el datasheet del <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> vamos a encontrarnos que tiene 8 líneas de IRQ, de IRQ0 a IRQ7, y está todo bárbaro hasta que tenemos 9 o más dispositivos que conectar simultáneamente porque nos quedamos cortos.
En algún momento de los 80s efectivamente se quedaron cortos con los dispositivos y como buenos ingenieros, le chantaron otro más, creando un esquema <strong>master/slave</strong> dos <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> conectados en cascada.</p>
<table>
<thead>
<tr>
<th style="text-align: left">Línea de Hardware</th>
<th style="text-align: left">8259a PIC Chip</th>
<th style="text-align: left">Vector Default</th>
<th style="text-align: left">Remapeado Modo Protegido</th>
<th style="text-align: left">Dispositivo Asignado</th>
<th style="text-align: left">Puerto I/O Usado</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><strong>IRQ 0</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x08</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code></td>
<td style="text-align: left">Programmable Interrupt Timer (PIT)</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 1</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x09</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x21</code></td>
<td style="text-align: left">PS/2 Keyboard</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 2</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0A</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x22</code></td>
<td style="text-align: left">Cascade Line (Connects Slave PIC)</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 3</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0B</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x23</code></td>
<td style="text-align: left">COM2 / COM4 Serial Ports</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 4</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0C</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x24</code></td>
<td style="text-align: left">COM1 / COM3 Serial Ports</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 5</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0D</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x25</code></td>
<td style="text-align: left">LPT2 Parallel Port / Sound Card</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 6</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0E</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x26</code></td>
<td style="text-align: left">Floppy Disk Controller</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 7</strong></td>
<td style="text-align: left">Master</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x0F</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x27</code></td>
<td style="text-align: left">LPT1 Parallel Port (Spurious IRQ Line)</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x20</code> (Command), <code class="language-plaintext highlighter-rouge">0x21</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 8</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x70</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x28</code></td>
<td style="text-align: left">CMOS Real-Time Clock (RTC)</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 9</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x71</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x29</code></td>
<td style="text-align: left">ACPI / PCI Peripheral Extended</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 10</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x72</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2A</code></td>
<td style="text-align: left">Open PCI Peripheral Slot</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 11</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x73</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2B</code></td>
<td style="text-align: left">Open PCI Peripheral Slot</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 12</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x74</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2C</code></td>
<td style="text-align: left">PS/2 Mouse Port</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 13</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x75</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2D</code></td>
<td style="text-align: left">Floating Point Unit / Coprocessor</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 14</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x76</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2E</code></td>
<td style="text-align: left">Primary ATA / IDE Hard Drive</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
<tr>
<td style="text-align: left"><strong>IRQ 15</strong></td>
<td style="text-align: left">Slave</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x77</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0x2F</code></td>
<td style="text-align: left">Secondary ATA / IDE Hard Drive (Spurious)</td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">0xA0</code> (Command), <code class="language-plaintext highlighter-rouge">0xA1</code> (Data)</td>
</tr>
</tbody>
</table>
<p>Al usar el IRQ2 como <strong>Cascade Line</strong> entonces nos quedamos sin un IRQ pero los dispositivos attachados al <code class="language-plaintext highlighter-rouge">slave</code> pueden acceder a través de él.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Dispositivo → Slave → Master → CPU
Dispositivo → Master → CPU
</code></pre></div></div>
<p>Adicionalmente, el <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> permite <strong>enmascarar</strong> interrupciones. Si estamos atendiendo el teclado y no queremos que se nos cuele la disquetera en ese momento, le decimos al PIC "esta IRQ en particular" no le des bola.</p>
<p>Esto es lo que hago cuando configuro las IRQs: tOSh solo implementa 3 nomás:</p>
<ul>
<li><strong>IRQ0</strong> (timer / pit)</li>
<li><strong>IRQ1</strong> (teclado)</li>
<li><strong>IRQ6</strong> (floppy)</li>
</ul>
<p>Todas las Interrupciones están enmascaradas menos estas tres que las desenmascaro especialmente para poder ser atendidas:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// idt.c</span>
<span class="p">[...]</span>
<span class="kt">void</span> <span class="nf">interrupt_handler</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">number</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">irq</span> <span class="o">=</span> <span class="n">number</span> <span class="o">-</span> <span class="mi">32</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">irq</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
<span class="c1">// timer (IRQ 0)</span>
<span class="n">pit_irq_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
<span class="c1">// Keyboard (IRQ 1)</span>
<span class="n">kbd_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">6</span><span class="p">:</span>
<span class="c1">// Floppy (IRQ 6)</span>
<span class="n">floppy_irq_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">pic_eoi</span><span class="p">(</span><span class="n">irq</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="la-idt---interrupt-descriptor-table">La IDT - Interrupt Descriptor Table</h2>
<p>La <code class="language-plaintext highlighter-rouge">IDT</code> es una estructura de datos binaria específica para las arquitecturas IA-32 y x86-64 y es la contrapartida del <a href="https://wiki.osdev.org/Real_Mode">Modo Real</a> y el <a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a> <code class="language-plaintext highlighter-rouge">IVT</code>.
Configurar la <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> nos permite decirle a la CPU donde están nuestras <a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a> o Interrupt Services Routines, que son las funciones que tenemos que escribir nosotros para poder manejar las interrupciones que el procesador o dispositivos disparen. Es bastante parecida a la estructura de la <a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a>.</p>
<p>A continuación va la tabla de Interrupciones del <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a> de Intel:</p>
<table>
<thead>
<tr>
<th>Int. Nº (hex)</th>
<th style="text-align: right">Int. Nº (dec)</th>
<th>Mnem.</th>
<th>Type</th>
<th>Err. code</th>
<th>Name</th>
<th>Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00</td>
<td style="text-align: right">0</td>
<td>#DE</td>
<td>Fault</td>
<td>No</td>
<td>Divide Error</td>
<td>DIV and IDIV instructions.</td>
</tr>
<tr>
<td>0x01</td>
<td style="text-align: right">1</td>
<td>#DB</td>
<td>Trap</td>
<td>No</td>
<td>Debug Exception</td>
<td>Instruction, data, and I/O breakpoints; single-step; and others.</td>
</tr>
<tr>
<td>0x02</td>
<td style="text-align: right">2</td>
<td>NMI</td>
<td>Interrupt</td>
<td>No</td>
<td>NMI Interrupt</td>
<td>Nonmaskable external interrupt.</td>
</tr>
<tr>
<td>0x03</td>
<td style="text-align: right">3</td>
<td>#BP</td>
<td>Trap</td>
<td>No</td>
<td>Breakpoint</td>
<td>INT3 instruction.</td>
</tr>
<tr>
<td>0x04</td>
<td style="text-align: right">4</td>
<td>#OF</td>
<td>Trap</td>
<td>No</td>
<td>Overflow</td>
<td>INTO instruction.</td>
</tr>
<tr>
<td>0x05</td>
<td style="text-align: right">5</td>
<td>#BR</td>
<td>Fault</td>
<td>No</td>
<td>BOUND Range Exceeded</td>
<td>BOUND instruction.</td>
</tr>
<tr>
<td>0x06</td>
<td style="text-align: right">6</td>
<td>#UD</td>
<td>Fault</td>
<td>No</td>
<td>Invalid Opcode (Undefined Opcode)</td>
<td>UD instruction or reserved opcode.</td>
</tr>
<tr>
<td>0x07</td>
<td style="text-align: right">7</td>
<td>#NM</td>
<td>Fault</td>
<td>No</td>
<td>Device Not Available (No Math Coprocessor)</td>
<td>Floating-point or WAIT/FWAIT instruction.</td>
</tr>
<tr>
<td>0x08</td>
<td style="text-align: right">8</td>
<td>#DF</td>
<td>Abort</td>
<td>Yes (zero)</td>
<td>Double Fault</td>
<td>Any instruction that can generate an exception, an NMI (Non-maskable interrupt), or an INTR (external interrupt).</td>
</tr>
<tr>
<td>0x09</td>
<td style="text-align: right">9</td>
<td>—</td>
<td>Fault</td>
<td>No</td>
<td>Coprocessor Segment Overrun (reserved)</td>
<td>Floating-point instruction.</td>
</tr>
<tr>
<td>0x0A</td>
<td style="text-align: right">10</td>
<td>#TS</td>
<td>Fault</td>
<td>Yes</td>
<td>Invalid TSS</td>
<td>Task switch or TSS access.</td>
</tr>
<tr>
<td>0x0B</td>
<td style="text-align: right">11</td>
<td>#NP</td>
<td>Fault</td>
<td>Yes</td>
<td>Segment Not Present</td>
<td>Loading segment registers or accessing system segments.</td>
</tr>
<tr>
<td>0x0C</td>
<td style="text-align: right">12</td>
<td>#SS</td>
<td>Fault</td>
<td>Yes</td>
<td>Stack-Segment Fault</td>
<td>Stack operations and SS (stack segment) register loads.</td>
</tr>
<tr>
<td>0x0D</td>
<td style="text-align: right">13</td>
<td>#GP</td>
<td>Fault</td>
<td>Yes</td>
<td>General Protection</td>
<td>Any memory reference and other protection checks.</td>
</tr>
<tr>
<td>0x0E</td>
<td style="text-align: right">14</td>
<td>#PF</td>
<td>Fault</td>
<td>Yes</td>
<td>Page Fault</td>
<td>Any memory reference.</td>
</tr>
<tr>
<td>0x0F</td>
<td style="text-align: right">15</td>
<td>—</td>
<td>—</td>
<td>No</td>
<td>Intel reserved. Do not use.</td>
<td>—</td>
</tr>
<tr>
<td>0x10</td>
<td style="text-align: right">16</td>
<td>#MF</td>
<td>Fault</td>
<td>No</td>
<td>x87 FPU Floating-Point Error (Math Fault)</td>
<td>x87 FPU floating-point or WAIT/FWAIT instruction.</td>
</tr>
<tr>
<td>0x11</td>
<td style="text-align: right">17</td>
<td>#AC</td>
<td>Fault</td>
<td>Yes (zero)</td>
<td>Alignment Check</td>
<td>Any data reference in memory.</td>
</tr>
<tr>
<td>0x12</td>
<td style="text-align: right">18</td>
<td>#MC</td>
<td>Abort</td>
<td>No</td>
<td>Machine Check</td>
<td>Error codes (if any) and source are model dependent.</td>
</tr>
<tr>
<td>0x13</td>
<td style="text-align: right">19</td>
<td>#XM</td>
<td>Fault</td>
<td>No</td>
<td>SIMD Floating-Point Exception</td>
<td>SSE/SSE2/SSE3 floating-point instructions.</td>
</tr>
<tr>
<td>0x14</td>
<td style="text-align: right">20</td>
<td>#VE</td>
<td>Fault</td>
<td>No</td>
<td>Virtualization Exception</td>
<td>EPT violations.</td>
</tr>
<tr>
<td>0x15</td>
<td style="text-align: right">21</td>
<td>#CP</td>
<td>Fault</td>
<td>Yes</td>
<td>Control Protection Exception</td>
<td>RET, IRET, RSTORSSP, and SETSSBSY instructions can generate this exception. When CET indirect branch tracking is enabled, this exception can be generated due to a missing ENDBRANCH instruction at target of an indirect call or jump.</td>
</tr>
<tr>
<td>0x16–0x1F</td>
<td style="text-align: right">22–31</td>
<td>—</td>
<td>—</td>
<td>—</td>
<td>Reserved for future use as CPU exception vectors.</td>
<td>—</td>
</tr>
<tr>
<td>0x20–0xFF</td>
<td style="text-align: right">32–255</td>
<td>—</td>
<td>Interrupt</td>
<td>No</td>
<td>—</td>
<td>External interrupts.</td>
</tr>
</tbody>
</table>
<p><strong>Un error común es confundir los IRQ del <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> con las INTerrupciones. NO SON LO MISMO!!!!!1</strong>
Una IRQ es la línea física del hardware. El número de interrupción es la entrada que usa el CPU en la IDT.
Son dos cosas relacionadas, pero no son lo mismo, y el que las conecta es justamente el <strong>remapping</strong> del PIC.</p>
<p>El número de interrupción funciona como índice dentro de la tabla: llega la interrupción, el CPU busca la entrada correspondiente y salta directo al código que configuramos para atenderla.</p>
<h2 id="remapping">Remapping</h2>
<p>Ya se está oliendo el problema, ¿verdad? Los valores de los IRQ y las Interrupciones del CPU (QUE NO SON LO MISMO!!!!!!!!!!1) se solapan.
Este solapamiento hace que tengamos que mover o <strong>remapear</strong> los valores de los IRQ después de la tabla de INTerrupciones del CPU.
Las primeras 32 entradas de la <a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> (vectores del 0 al 31) están reservadas para excepciones del propio <code class="language-plaintext highlighter-rouge">x86</code> (<em>divide by zero error, general protection fault, etc..</em>) y los IRQ del PIC también arrancan desde del 0, solapándose.</p>
<p>Por eso al arrancar nuestro <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> tenemos que reconfigurar el PIC para que corra arriba de la zona de la tabla de <strong>INTerrupciones</strong> del CPU de la siguiente manera:</p>
<table>
<thead>
<tr>
<th style="text-align: right">PIC</th>
<th style="text-align: right">IRQ Original</th>
<th style="text-align: right">IRQ Destino</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right">Master</td>
<td style="text-align: right">IRQ0 - IRQ7</td>
<td style="text-align: right">INT 32 - INT 39</td>
</tr>
<tr>
<td style="text-align: right">Slave</td>
<td style="text-align: right">IRQ8 - IRQ15</td>
<td style="text-align: right">INT 40 - INT 47</td>
</tr>
</tbody>
</table>
<h2 id="presione-cualquier-tecla-para-continuar">Presione cualquier tecla para continuar…</h2>
<p>Pongamos como ejemplo, presionar la tecla <code class="language-plaintext highlighter-rouge">cualquiera</code>.
La lógica de cualquier teclado de PC, por cada tecla presionada, genera un <code class="language-plaintext highlighter-rouge">scancode</code> al controlador del teclado.
Este controlador genera un <code class="language-plaintext highlighter-rouge">IRQ</code>, que en nuestro caso, es el <code class="language-plaintext highlighter-rouge">IRQ1</code> (ver tabla más arriba). El que recibe este <code class="language-plaintext highlighter-rouge">IRQ</code> es el <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> que verifica que la interrupción esté habilitada (o desenmascarada), la marca como pendiente y le manda una señal al CPU mediante la línea INTR.
La CPU acepta la interrupción y el PIC deliverea el vector correspondiente. Si la IRQ1 esta remapeada al vector 0x21, la CPU usa ese vector para consultar la IDT y ver en que posición de memoria va a encontrar el <strong>manejador de interrupción</strong> correspondiente (interrupt handler).</p>
<p>Este es el de <code class="language-plaintext highlighter-rouge">tOSh</code>, solamente soportamos un Timer, el Teclado y el Floppy:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* de idt.asm, declaramos esta y otras funciones como "extern" */</span>
<span class="kt">void</span> <span class="nf">interrupt_handler</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">number</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">irq</span> <span class="o">=</span> <span class="n">number</span> <span class="o">-</span> <span class="mi">32</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">irq</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
<span class="c1">// timer (IRQ 0)</span>
<span class="n">pit_irq_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
<span class="c1">// Keyboard (IRQ 1)</span>
<span class="n">kbd_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">6</span><span class="p">:</span>
<span class="c1">// Floppy (IRQ 6)</span>
<span class="n">floppy_irq_handler</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">pic_eoi</span><span class="p">(</span><span class="n">irq</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Y desde las rutinas en <code class="language-plaintext highlighter-rouge">idt.asm</code> donde declaramos como vamos a cargar el handler, creamos esta función:</p>
<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>
<span class="nf">extern</span> <span class="nv">interrupt_handler</span>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>
<span class="nl">irq_common:</span>
<span class="nf">pusha</span>
<span class="c1">; ESP apunta ahora al comienzo de pusha:</span>
<span class="c1">;</span>
<span class="c1">; [ESP + 0] EDI</span>
<span class="c1">; [ESP + 4] ESI</span>
<span class="c1">; [ESP + 8] EBP</span>
<span class="c1">; [ESP + 12] original ESP</span>
<span class="c1">; [ESP + 16] EBX</span>
<span class="c1">; [ESP + 20] EDX</span>
<span class="c1">; [ESP + 24] ECX</span>
<span class="c1">; [ESP + 28] EAX</span>
<span class="c1">; [ESP + 32] interrupt number</span>
<span class="c1">; [ESP + 36] error code</span>
<span class="c1">; [ESP + 40] EIP</span>
<span class="c1">; [ESP + 44] CS</span>
<span class="c1">; [ESP + 48] EFLAGS</span>
<span class="nf">mov</span> <span class="nb">eax</span><span class="p">,</span> <span class="p">[</span><span class="nb">esp</span> <span class="o">+</span> <span class="mi">32</span><span class="p">]</span> <span class="c1">; +32 = interrupt number </span>
<span class="nf">push</span> <span class="nb">eax</span>
<span class="nf">call</span> <span class="nv">interrupt_handler</span>
<span class="nf">add</span> <span class="nb">esp</span><span class="p">,</span> <span class="mi">4</span>
<span class="nf">popa</span>
<span class="c1">; remove interrupt number + fake error code</span>
<span class="nf">add</span> <span class="nb">esp</span><span class="p">,</span> <span class="mi">8</span>
<span class="nf">iret</span>
<span class="p">[</span><span class="nf">...</span><span class="p">]</span>
</code></pre></div></div>
<p>La CPU entra al handler correspondiente a través de nuestro <code class="language-plaintext highlighter-rouge">extern interrupt_handler</code> que es llamado en <code class="language-plaintext highlighter-rouge">irq_common</code> y hace un call a nuestro código en C que termina ejecutando la función <code class="language-plaintext highlighter-rouge">kbd_handler()</code>.
Como se puede ver, guardamos con <code class="language-plaintext highlighter-rouge">pusha</code> el contexto de ejecución porque cualquier cosa que haya estado haciendo la CPU, la <strong>tenemos que salvar</strong> para hacer lo que tengamos que hacer y al volver de nuestro <code class="language-plaintext highlighter-rouge">call interrupt_handler</code>
recuperar el contexto y volver con <code class="language-plaintext highlighter-rouge">iret</code> de nuestra interrupción.</p>
<p>Tenemos todo cubierto. El <code class="language-plaintext highlighter-rouge">kbd_handler()</code> y todo lo que tiene que ver con el teclado es bastante un embole porque es mapear <code class="language-plaintext highlighter-rouge">scancodes</code> con funciones de teclado (CTRL+C hacé esto, SHIFT + minúscula transforma a mayúscula, etc…) como <strong>phun phakt</strong>, este pedazo de <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> me llevo años desarrollarlo porque al ver lo que tenía que codear, me daba paja y por un par de años me dio cosa copiar algunas implementaciones piolas que encontré de otras personas en GitHub, pero hace poco se me pasó y me inspiré muy fuertemente. Lamentablemente hoy, no recuerdo bien de quienes, pero hey, gracias.</p>
<p>Como pueden ver en la funcion C <code class="language-plaintext highlighter-rouge">interrupt_handler()</code> hay una llamada a la función <code class="language-plaintext highlighter-rouge">pic_eoi()</code>. No basta con un <code class="language-plaintext highlighter-rouge">iret</code> para indicar que se está volviendo de una interrupción, también hay que decírselo al PIC para decirle que terminamos de atender la <code class="language-plaintext highlighter-rouge">IRQ</code> también.</p>
<p>La CPU finalmente, vuelve al programa que fue interrumpido, continuando exactamente en el <code class="language-plaintext highlighter-rouge">EIP</code> en la que se había detenido.</p>
<p>Y listo. Terminamos.</p>
<h2 id="wh0a-te-falta-el-apic-advanced-programmable-interrupt-controller">¡wh0a, te falta el APIC! (Advanced Programmable Interrupt Controller)</h2>
<p>¡Ah no! ¡Pará! Me queda hacer una aclaración. <a href="https://wiki.osdev.org/APIC">APIC</a> es el reemplazo del <a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a> y hasta hoy, <code class="language-plaintext highlighter-rouge">tOSh</code> no lo soporta.
De alguna manera, soportar <a href="https://wiki.osdev.org/APIC">APIC</a> permitiría poder manejar entre todas las cosas que soporta, más CPUs (dualcore en adelante), pero en este momento del desarrollo me estoy peleando con enmascaramiento de interrupciones y programar un teclado.</p>
<p>Así que lo dejamos para más adelante. :)</p>
<h1 id="referencias">Referencias</h1>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Polling_(computer_science)">Polling</a></li>
<li><a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a></li>
<li><a href="https://wiki.osdev.org/Setting_Up_Long_Mode">Long Mode</a></li>
<li><a href="https://wiki.osdev.org/Real_Mode">Modo Real</a></li>
<li><a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></li>
<li><a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a></li>
<li><a href="https://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a></li>
<li><a href="https://wiki.osdev.org/Interrupt_Service_Routines">ISR</a></li>
<li><a href="https://wiki.osdev.org/Linker_Scripts">Linker Scripts</a></li>
<li><a href="https://www.eeeguide.com/8259a-programmable-interrupt-controller/">Chip 8259a PIC</a></li>
<li><a href="https://wiki.osdev.org/8259_PIC">8259 PIC osdev</a></li>
<li><a href="https://wiki.osdev.org/APIC">APIC</a></li>
<li><a href="https://wiki.osdev.org/X86_Interrupts">Interrupciones</a></li>
</ul>
tOSh para x86 - i386+ | Parte III | Saltando a C2020-06-23T07:43:04+00:00http://example.com/saltando-a-c<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p>
<h1 id="partes">Partes</h1>
<p>Esta serie de artículos tiene las siguientes partes:</p>
<ul>
<li><a href="/codeando-un-bootloader">Parte I - Bootloader</a></li>
<li><a href="/modo-protegido">Parte II - Stage 1</a></li>
<li><a href="/saltando-a-c">Parte III - Saltando a C</a> <strong><— Estás acá</strong></li>
<li><a href="/interrupciones">Parte IV - Interrupciones</a></li>
<li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a></li>
<li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</a></li>
</ul>
<p><img src="/assets/images/tOSh/armando-pc-1997-2021.jpeg" alt="Armando Máquina de 1997" class="img-responsive" /><em>Armando Máquina de 1997</em></p>
<h1 id="saltando-a-c">Saltando a C</h1>
<p>A veces, mientras escribo esta serie, me hago la pregunta que si es necesario estar explicando todos los conceptos de cada palabra especializada
que uso como <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> y nunca sé bien cuán profundo tengo que ir en mis artículos. Después lleno los términos de links y se me pasa.
Mientras escribo exactamente esta oración me estoy preguntando: ¿Tengo que explicar que es un <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a>? Me parece que no. Si estás leyendo
esto, asumo que sabés un montón. Y si no, igual te lleno de links por cada término técnico que uso para que puedas seguir investigando por tu cuenta.</p>
<p>Bien. Tenemos que armar un <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a>, porque ya estamos saltando a él, pero no hablamos de nada sobre como empezar a escribir bytes para un sistema operativo
desde C.</p>
<p>Lo primero y quizá lo más importante de todo lo que tenés que saber sobre desarrollo de Kernel es que no podés programar así nomás, pim pam pum,
agarro un <code class="language-plaintext highlighter-rouge">gcc</code> o un <code class="language-plaintext highlighter-rouge">clang</code> ¡paf! le chanto un <code class="language-plaintext highlighter-rouge">main.c</code> y a compilar y meter el binario donde se te cante.
¡Noooooo! Lo que necesitás hacer primero es prepararte un <code class="language-plaintext highlighter-rouge">crosscompiler</code>, es decir, un compilador que permita escribir binarios para otras
arquitecturas sin depender de las bibliotecas que vienen con el sistema operativo o sus <a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a> objetivo porque no hay <a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a>.
Es decir, no hay librerias, no hay interfaces, no hay syscalls, no hay nada porque no tenés kernel, ¡porque estás haciendo uno!</p>
<p>Parece una obviedad mencionar esto, pero si nos detenemos un segundo a pensar, uno está acostumbrado a tener cientos
de capas de abstracción que evitan estar interactuando con el CPU y resolver cientos de problemas que se computan casi mágicamente
con una o dos llamadas a función. Hacer un <code class="language-plaintext highlighter-rouge">printf</code> estándar de C es sorprendentemente complicado y es un <strong>rabbit hole</strong> en el cual
hoy no, pero algún día entraré.</p>
<p>Dicho todo esto recomiendo fuertemente leer el artículo <a href="https://wiki.osdev.org/GCC_Cross-Compiler">GCC Cross-compiler</a> de <code class="language-plaintext highlighter-rouge">osdev</code>.</p>
<h2 id="de-kmain-linkers-y-bash">De KMain, Linkers y Bash</h2>
<p>Este va a ser nuestro <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> por ahora. Lo único que va a hacer es escribir el caracter "X"
en la posición de memoria <strong>VGA xb8000</strong> que es el área de memoria donde la placa de video está mirando
su contenido para replicarlo en el monitor correspondiente y eventualmente terminamos crasheando
el CPU porque seguimos escribiendo linearmente hasta 2^32 - 1 o mejor aún, hasta el límite de la memoria
que tengamos instalada en el hardware.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".text.kmain_entrypoint"</span><span class="p">)))</span>
<span class="kt">int</span> <span class="nf">kmain</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span> <span class="n">vga</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0xb8000</span><span class="p">;</span>
<span class="o">*</span><span class="n">vga</span> <span class="o">=</span> <span class="sc">'X'</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">){</span>
<span class="o">*</span><span class="p">(</span><span class="n">vga</span><span class="o">++</span><span class="p">)</span> <span class="o">=</span><span class="sc">'X'</span><span class="p">;</span>
<span class="p">};</span>
<span class="kt">char</span> <span class="o">*</span> <span class="n">str</span> <span class="o">=</span> <span class="s">"end of kernel code."</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Compilar este código requiere por lo menos el siguiente <code class="language-plaintext highlighter-rouge">Makefile</code>.
Primero definimos donde tenemos la instalación de nuestra versión Crosscompilada de <strong>GCC</strong> que compilamos
siguiendo las instrucciones en del artículo <a href="https://wiki.osdev.org/GCC_Cross-Compiler">GCC Cross-compiler</a> de <code class="language-plaintext highlighter-rouge">osdev</code>.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ASM</span><span class="o">=</span>nasm
<span class="nv">CROSS_DIR</span><span class="o">=</span>~/opt/cross/bin
<span class="nv">CC</span><span class="o">=</span><span class="p">$(</span>CROSS_DIR<span class="p">)</span>/i686-elf-gcc
<span class="nv">AS</span><span class="o">=</span><span class="p">$(</span>CROSS_DIR<span class="p">)</span>/i686-elf-as
<span class="nv">LD</span><span class="o">=</span><span class="p">$(</span>CROSS_DIR<span class="p">)</span>/i686-elf-ld
</code></pre></div></div>
<p>Luego seteamos los siguientes flags del compilador:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-m32</code> le dice a <strong>GCC</strong> que genere código <strong>32 bits</strong>. Es decir,
registros generales en 32 bits (eax, ebx, ebp, esp, edi, esi, etc…),
punteros de 32 bits, ABI de 32 bits, instrucciones compatibles con el modo protegido de 32 bits.</li>
<li><code class="language-plaintext highlighter-rouge">-std=gnu99</code> es la versión <strong>c99</strong> del estándar C con extensiones GNU, del año 1999.</li>
<li><code class="language-plaintext highlighter-rouge">-Ttext 0x10000</code> flag de linker, le dice al linker que la sección <code class="language-plaintext highlighter-rouge">.text</code> comienza en la dirección <code class="language-plaintext highlighter-rouge">0x10000</code></li>
<li><code class="language-plaintext highlighter-rouge">-ffreestanding</code> le decimos a <strong>GCC</strong> que no asuma que tenemos funciones estandar del runtime de C como <code class="language-plaintext highlighter-rouge">malloc()</code>, <code class="language-plaintext highlighter-rouge">printf()</code>, etc…</li>
<li><code class="language-plaintext highlighter-rouge">-fno-pic</code> desactivamos <strong>Position Independent Code</strong>, con saber que todo el <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> arranca en <code class="language-plaintext highlighter-rouge">0x10000</code> en adelante es suficiente.</li>
<li><code class="language-plaintext highlighter-rouge">-fno-asynchronous-unwind-tables</code> y <code class="language-plaintext highlighter-rouge">-fno-unwind-tables</code> desactivamos la metadata que genera GCC para reconstruir excepciones, tracebacks, stacktraces,
en caso de error. Reducimos el tamaño del binario con esto pero nos limitamos las capacidades para debuggear también.</li>
<li><code class="language-plaintext highlighter-rouge">-march=i486</code> reducimos el set de instrucciones bien atrás hasta 486 para evitar instrucciones avanzadas
(onda, la instruciones de la familia pentium para arriba) limitando la compatibilidad con CPUs más viejas. Por ahora, <a href="/resources/">tOSh</a>
corre a partir de <code class="language-plaintext highlighter-rouge">i386</code>.</li>
<li><code class="language-plaintext highlighter-rouge">-Map kernel.map</code> es una opción del <strong>linker</strong> y nos sirve para crear un mapa de memoria y ubicar en que offsets está cada función y sección del binario</li>
</ul>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">CCFLAGS</span><span class="o">=</span> <span class="nt">-m32</span> <span class="nt">-std</span><span class="o">=</span>gnu99 <span class="nt">-Ttext</span> 0x10000
<span class="nv">CCFLAGS</span><span class="o">+=</span> <span class="nt">-ffreestanding</span> <span class="nt">-O0</span> <span class="nt">-Wall</span> <span class="nt">-Wextra</span> <span class="nt">-fno-pic</span>
<span class="nv">CCFLAGS</span><span class="o">+=</span> <span class="nt">-fno-asynchronous-unwind-tables</span>
<span class="nv">CCFLAGS</span><span class="o">+=</span> <span class="nt">-fno-unwind-tables</span>
<span class="nv">CCFLAGS</span><span class="o">+=</span> <span class="nt">-march</span><span class="o">=</span>i486 <span class="c"># avoid instructions like cmovna to be generated by gcc</span>
<span class="nv">LDFLAGS</span><span class="o">=</span> <span class="nt">-Map</span> kernel.map
</code></pre></div></div>
<p>Finalmente definimos un <code class="language-plaintext highlighter-rouge">Makefile</code> estandar. No hay nada loco aca, tenemos <code class="language-plaintext highlighter-rouge">.o</code>, <code class="language-plaintext highlighter-rouge">.map</code>, y
algo especial que es nuestro archivo <code class="language-plaintext highlighter-rouge">linker.ld</code>.</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">KERNEL_SRC</span><span class="o">=</span><span class="p">$(</span>wildcard kernel/<span class="k">*</span>.c<span class="p">)</span>
<span class="nv">KERNEL_OBJS</span><span class="o">=</span><span class="p">$(</span>KERNEL_SRC:.c<span class="o">=</span>.o<span class="p">)</span>
<span class="nv">KERNEL_BIN</span><span class="o">=</span>kernel.bin
<span class="nl">all</span><span class="o">:</span> <span class="nf">kernel </span>
<span class="nl">clean</span><span class="o">:</span>
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">*</span>.bin
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">*</span>.o
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">*</span>.map
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">*</span>.img
<span class="nb">rm</span> <span class="nt">-rf</span> kernel/<span class="k">*</span>.o
<span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">*</span>.lst
<span class="nl">%.o</span><span class="o">:</span> <span class="nf">%.c</span>
<span class="p">$(</span>CC<span class="p">)</span> <span class="nt">-o</span> <span class="nv">$@</span> <span class="nt">-c</span> <span class="nv">$<</span> <span class="p">$(</span>CCFLAGS<span class="p">)</span>
<span class="nl">kernel</span><span class="o">:</span> <span class="nf">$(KERNEL_OBJS)</span>
<span class="p">$(</span>LD<span class="p">)</span> <span class="nt">-o</span> <span class="p">$(</span>KERNEL_BIN<span class="p">)</span> <span class="nv">$^</span> <span class="p">$(</span>LDFLAGS<span class="p">)</span> <span class="nt">-Tkernel</span>/linker.ld
</code></pre></div></div>
<h2 id="el-script-del-linker">El script del Linker</h2>
<p>Cuando compilás, <strong>GCC</strong> genera unos archivos <code class="language-plaintext highlighter-rouge">.o</code> que contienen código y datos pero cada uno está pensado como un cacho independiente
del otro. Además, cada <code class="language-plaintext highlighter-rouge">.o</code> tiene varias secciones, como <code class="language-plaintext highlighter-rouge">.text</code>, <code class="language-plaintext highlighter-rouge">.rodata</code>, <code class="language-plaintext highlighter-rouge">.bss</code>, etc…
El linker lo que hace es agarrar todos esos <code class="language-plaintext highlighter-rouge">.o</code> que se generan al compilar uno o varios archivos <code class="language-plaintext highlighter-rouge">.c</code> y construye un programa
ubicando cada uno de esos <strong>Código OBJeto</strong> y posicionarlos decidiendo donde quedan exactamente cada uno de ellos en memoria.
Adicionalmente, resuelve referencias. Si la funcion <code class="language-plaintext highlighter-rouge">pepito_delicioso()</code> está programada en <code class="language-plaintext highlighter-rouge">mi_libreria_de_pepitos.c</code>, ese código
va a estar en <code class="language-plaintext highlighter-rouge">mi_libreria_de_pepitos.o</code> y si mi funcion <code class="language-plaintext highlighter-rouge">kmain.c</code> la llama, el linker se encarga de resolver esas referencia
a esa función.</p>
<p>El archivo <code class="language-plaintext highlighter-rouge">linker.ld</code> es un <strong>linker script</strong> y es un archivo que le dice al linker que no ubique los <strong>OBJ</strong> como se le cante,
sino más bien, vos le decís donde querés que ubique cada cosa en memoria. Definiendo secciones nuevas o donde empieza <code class="language-plaintext highlighter-rouge">.text</code>
uno tiene más control del <strong>layout</strong> de tu programa en memoria.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>OUTPUT_FORMAT("binary")
/*ENTRY(kmain)*/
SECTIONS
{
/* the start address of the kernel's .text section */
. = 0x10000;
.text BLOCK(4K) : ALIGN(4K)
{
/* *(.text.prologue) */
/*
set up the program entry point
kmain.c should have only one function,
and it's going to be the first section of code executed
when jumping to the kernel.
in the generated kernel.map file, kernel/kmain.o should be
set at address 0x0000000000010000
and void kmain(void); should be the first function written on top
of the program
*/
*(.text.kmain_entrypoint)
/* and later on, all the kernel objects */
*(.text)
}
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
}
.data BLOCK(4K) : ALIGN(4K)
{
*(.data)
}
.bss BLOCK(4K) : ALIGN(4K)
{
*(.bss)
}
.idt BLOCK(4K) : ALIGN(4K)
{
*(.idt)
}
.dma BLOCK(4K) : ALIGN(4K)
{
*(.dma)
}
end = .;
}
</code></pre></div></div>
<p>Al principio de todo, definimos <code class="language-plaintext highlighter-rouge">OUTPUT_FORMAT(\"binary\")</code>. Esto hace que el linker de gcc, no genere un archivo ejecutable <code class="language-plaintext highlighter-rouge">.elf</code> sino
un <code class="language-plaintext highlighter-rouge">.bin</code> directamente que no tiene ni headers, ni tablas, ni simbolos de debugging, nada. Un binario plano.
Nuestro <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> no sabe nada de formatos binarios ni nada y si saltamos a un <code class="language-plaintext highlighter-rouge">.elf</code> el CPU va a ejecutar
los bytes del <strong>header del formato ELF</strong> crasheando el CPU.</p>
<p>Con <code class="language-plaintext highlighter-rouge">. = 0x10000;</code> dentro de <code class="language-plaintext highlighter-rouge">SECTIONS</code> le decimos a linker que empiece a ubicar los binarios a partir de esa dirección.
Esto apunta a <code class="language-plaintext highlighter-rouge">.text</code>, o sea que lo que le pasamos al <code class="language-plaintext highlighter-rouge">Makefile</code> donde le decimos donde empieza la seccion <code class="language-plaintext highlighter-rouge">.text</code> es redundante.</p>
<p>Despues, cada una de las secciones tienen que ser bloques y ademas estar alineados en "páginas de 4 kilobytes".
Este tamaño, 4Kb, es el definido como página en x86. No solo las secciones tienen que estar en bloques de 4kb, sino que además
tienen que estar alineados de a bloques de 4kb.</p>
<p>La primera sección ejecutable se llama: <code class="language-plaintext highlighter-rouge">.text.kmain_entrypoint</code> y lo que hacemos definiendolo asi es que sea cual fuere
la función en C que tenga este atributo, se le chanta ahi, que es la dirección de memoria <code class="language-plaintext highlighter-rouge">0x10000</code> que ya habíamos definido
y de esa manera nos aseguramos que esa funcion sea la primer función que se ejecute.</p>
<p>Si te fijás más arriba, en el código en C que presente primero, vas a ver arriba de <code class="language-plaintext highlighter-rouge">kmain()</code>el attributo que el linker
va a tomar del script para que sea lo primero que se ejecute de todo:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
0x10000 <-- arrancando en esta dirección de memoria
*(.text.kmain_entrypoint) <-- primero mete esta función
*(.text) <-- despues el resto de funciones
+-------------------------------+
| kmain_entrypoint | <- PRIMERO
+-------------------------------+
| otras funciones .text |
| configurar_cosas() |
| contemplar_existencia() |
| handlear_interrupciones() |
| etc. |
+-------------------------------+
*/</span>
<span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".text.kmain_entrypoint"</span><span class="p">)))</span>
<span class="kt">void</span> <span class="nf">kmain</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="p">[...]</span> <span class="p">}</span>
</code></pre></div></div>
<p>Las secciones siguientes como <code class="language-plaintext highlighter-rouge">.bss</code>, <code class="language-plaintext highlighter-rouge">.rodata</code> y <code class="language-plaintext highlighter-rouge">.data</code> son estandard pero las que quedan no, son específicas del diseño de
mi <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a>.</p>
<p>La sección <code class="language-plaintext highlighter-rouge">.idt</code> la vamos a necesitar en un futuro cuando definamos la tabla de interrupciones de alguna manera como la siguiente:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .idt BLOCK(4K) : ALIGN(4K)
{
*(.idt)
}
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".idt"</span><span class="p">)))</span>
<span class="k">struct</span> <span class="n">idt_entry</span> <span class="n">idt</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
</code></pre></div></div>
<p>Lo mismo hacemos con la sección <code class="language-plaintext highlighter-rouge">.dma</code> que vamos a usar en un futuro también para guardar unos bùferes:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.dma BLOCK(4K) : ALIGN(4K)
{
*(.dma)
}
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".dma"</span><span class="p">)))</span>
<span class="kt">uint8_t</span> <span class="n">dma_buffer</span><span class="p">[...];</span>
</code></pre></div></div>
<p>Entonces más o menos el layout de memoria del <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> quedaría más o menos así:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x10000
+--------------------------+
| .text |
| |
| kmain() | <- primera función de todas, el entrypoint
| fs_init() |
| fs_read() |
| idt_init() |
| ... |
+--------------------------+
| .rodata |
| strings / constantes |
+--------------------------+
| .data |
| variables inicializadas |
+--------------------------+
| .bss |
| variables sin init |
+--------------------------+
| .idt |
| IDT |
+--------------------------+
| .dma |
| DMA buffers |
+--------------------------+
| |
| end |
+--------------------------+
</code></pre></div></div>
<h2 id="la-gotita">La Gotita</h2>
<p>Finalmente para unir y pegar con "La Gotita" todas las piezas del rompecabezas y tener una imagen para chantarle a <code class="language-plaintext highlighter-rouge">qemu</code>, <code class="language-plaintext highlighter-rouge">bochs</code> o una máquina real, necesitamos generar una imagen donde esté el <em>bootloader, stage1 y el kernel</em> y cada uno de sus binarios estén ubicados de manera tal el bootloader y el stage1 copien las cosas en memoria y que en el proceso de recompilar y programar, no se pisen los binarios entre si y estén lo suficientemente equiespaciados.</p>
<p>Para lograr esto, tuvimos que sentarnos con un papel y decir cuanto <strong>espacio suficiente</strong> siendo <strong>"suficiente"</strong> una variable que cambió
bastante a lo largo del desarrollo de este <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a>. Más o menos este es mi layout del Floppy 3.5" 1.44MB que uso para meter
el Sistema Operativo en él (update agosto 2026):</p>
<table>
<thead>
<tr>
<th style="text-align: right">Sectores</th>
<th style="text-align: right">Offset</th>
<th>Contenido</th>
<th style="text-align: right">Tamaño</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right"><strong>1</strong></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">0x000000</code></td>
<td>Bootsector (<code class="language-plaintext highlighter-rouge">boot.bin</code>)</td>
<td style="text-align: right">512 B</td>
</tr>
<tr>
<td style="text-align: right"><strong>2–8</strong></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">0x000200–0x000FFF</code></td>
<td>Espacio reservado / Stage1</td>
<td style="text-align: right">hasta 7 sectores</td>
</tr>
<tr>
<td style="text-align: right"><strong>9–136</strong></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">0x001000–0x10FFF</code></td>
<td>Kernel (<code class="language-plaintext highlighter-rouge">kernel.bin</code>)</td>
<td style="text-align: right">hasta 128 sectores</td>
</tr>
<tr>
<td style="text-align: right"><strong>137–2880</strong></td>
<td style="text-align: right"><code class="language-plaintext highlighter-rouge">0x11000–0x167FFF</code></td>
<td>Filesystem / espacio restante</td>
<td style="text-align: right">~1.37 MB</td>
</tr>
</tbody>
</table>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">echo</span> <span class="s2">"Cleaning up..."</span>
make clean
<span class="nb">echo</span> <span class="s2">"Compiling tOSh Kernel..."</span>
make
<span class="nv">kernel_size</span><span class="o">=</span><span class="si">$(</span><span class="nb">stat</span> <span class="nt">-c</span> %s kernel.bin<span class="si">)</span>
<span class="nv">kernel_sectors</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span>kernel_size <span class="o">+</span> <span class="m">511</span><span class="o">)</span> <span class="o">/</span> <span class="m">512</span> <span class="k">))</span>
<span class="nv">MAX_KERNEL_SECTORS</span><span class="o">=</span>128
<span class="nv">KERNEL_FIRST_SECTOR</span><span class="o">=</span>9
<span class="nb">echo</span> <span class="s2">"kernel size: </span><span class="nv">$kernel_size</span><span class="s2"> bytes, kernel sectors: </span><span class="nv">$kernel_sectors</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$kernel_sectors</span><span class="s2">"</span> <span class="nt">-gt</span> <span class="s2">"</span><span class="nv">$MAX_KERNEL_SECTORS</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">echo</span> <span class="s2">"ERROR: kernel too large!"</span>
<span class="nb">echo</span> <span class="s2">"Maximum: </span><span class="k">$((</span>MAX_KERNEL_SECTORS <span class="o">*</span> <span class="m">512</span><span class="k">))</span><span class="s2"> bytes"</span>
<span class="nb">exit </span>1
<span class="k">fi
</span><span class="nb">echo</span> <span class="s2">"Compiling bootsector and stage1..."</span>
<span class="nb">echo</span> <span class="s2">"taking into account the kernel size of </span><span class="nv">$kernel_size</span><span class="s2"> bytes, which is </span><span class="nv">$kernel_sectors</span><span class="s2"> sectors..."</span>
<span class="nb">echo</span> <span class="s2">"bootsector.asm -> boot.bin"</span>
nasm <span class="nt">-f</span> bin bootsector.asm <span class="nt">-o</span> boot.bin
<span class="nb">echo</span> <span class="s2">"Kernel first sector: </span><span class="nv">$KERNEL_FIRST_SECTOR</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"stage1.asm -> stage1.bin with KERNEL_FIRST_SECTOR=</span><span class="nv">$KERNEL_FIRST_SECTOR</span><span class="s2"> and KERNEL_SECTORS=</span><span class="nv">$kernel_sectors</span><span class="s2">"</span>
nasm <span class="nt">-D</span> <span class="nv">KERNEL_FIRST_SECTOR</span><span class="o">=</span><span class="nv">$KERNEL_FIRST_SECTOR</span> <span class="nt">-D</span> <span class="nv">KERNEL_SECTORS</span><span class="o">=</span><span class="nv">$kernel_sectors</span> <span class="nt">-f</span> bin <span class="nt">-l</span> stage1.lst stage1.asm <span class="nt">-o</span> stage1.bin
<span class="nv">stage1_size</span><span class="o">=</span><span class="si">$(</span><span class="nb">stat</span> <span class="nt">-c</span>%s stage1.bin<span class="si">)</span>
<span class="nv">stage1_sectors</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span>stage1_size <span class="o">+</span> <span class="m">511</span><span class="o">)</span> <span class="o">/</span> <span class="m">512</span> <span class="k">))</span>
<span class="nb">echo</span> <span class="s2">"Stage1 size: </span><span class="nv">$stage1_size</span><span class="s2"> bytes, </span><span class="nv">$stage1_sectors</span><span class="s2"> sectors"</span>
<span class="c">#nasm -f bin kernel/kernel.asm -o kernel.bin</span>
<span class="nb">echo</span> <span class="s2">"Creating floppy image..."</span>
<span class="nb">dd </span><span class="k">if</span><span class="o">=</span>/dev/zero <span class="nv">of</span><span class="o">=</span>tOSh.img <span class="nv">bs</span><span class="o">=</span>512 <span class="nv">count</span><span class="o">=</span>2880
<span class="nb">echo</span> <span class="s2">"Copying Bootsector at position 0, block 512 bytes..."</span>
<span class="nb">dd </span><span class="k">if</span><span class="o">=</span>boot.bin <span class="nv">of</span><span class="o">=</span>tOSh.img <span class="nv">conv</span><span class="o">=</span>notrunc <span class="c"># copy the bootsector</span>
<span class="nb">echo</span> <span class="s2">"Copying stage1 at position 1, block 512 bytes..."</span>
<span class="nb">dd </span><span class="k">if</span><span class="o">=</span>stage1.bin <span class="nv">of</span><span class="o">=</span>tOSh.img <span class="nv">conv</span><span class="o">=</span>notrunc <span class="nv">bs</span><span class="o">=</span>512 <span class="nv">seek</span><span class="o">=</span>1 <span class="c"># copy stage1</span>
<span class="nb">echo</span> <span class="s2">"Copying kernel at position </span><span class="k">$((</span>KERNEL_FIRST_SECTOR <span class="o">-</span><span class="m">1</span><span class="k">))</span><span class="s2"> (sector </span><span class="nv">$KERNEL_FIRST_SECTOR</span><span class="s2">), block 512 bytes..."</span>
<span class="nb">dd </span><span class="k">if</span><span class="o">=</span>kernel.bin <span class="nv">of</span><span class="o">=</span>tOSh.img <span class="nv">conv</span><span class="o">=</span>notrunc <span class="nv">bs</span><span class="o">=</span>512 <span class="nv">seek</span><span class="o">=</span><span class="k">$((</span><span class="nv">$KERNEL_FIRST_SECTOR</span> <span class="o">-</span><span class="m">1</span><span class="k">))</span> <span class="c"># copy kernel</span>
</code></pre></div></div>
<p>Ejecutando este script de bash <code class="language-plaintext highlighter-rouge">./build.sh</code> crea el sistema operativo en una imagen de floppy lista para ser booteada en una máquina
o un emulador. Hasta acá lo único que hacemos es escribir <code class="language-plaintext highlighter-rouge">X</code> en memoria (empezando por el mapping de VGA) hasta que la CPU crashea
por escribir fuera de los límites de memoria.</p>
<p>Este es el framework "básico" para poder empezara desarrollar un <a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a> en C. A partir de ahora podemos seguir configurando
la CPU, programarle drivers y aprovechar todas las bondades de un lenguaje de alto nivel como C abandonando así, el assembly.</p>
<h1 id="referencias">Referencias</h1>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Application_binary_interface">ABI</a></li>
<li><a href="https://en.wikipedia.org/wiki/Kernel_%28operating_system%29">Kernel</a></li>
<li><a href="https://wiki.osdev.org/GCC_Cross-Compiler">GCC Cross-compiler</a></li>
<li><a href="https://wiki.osdev.org/Linker_Scripts">Linker Scripts</a></li>
</ul>
tOSh para x86 - i386+ | Parte II | Stage 1 y Modo Protegido2020-06-23T07:43:04+00:00http://example.com/modo-protegido<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p>
<h1 id="partes">Partes</h1>
<p>Esta serie de artículos tiene las siguientes partes:</p>
<ul>
<li><a href="/codeando-un-bootloader">Parte I - Bootloader</a></li>
<li><a href="/modo-protegido">Parte II - Stage 1</a> <strong><— Estás acá</strong></li>
<li><a href="/saltando-a-c">Parte III - Saltando a C</a></li>
<li><a href="/interrupciones">Parte IV - Interrupciones</a></li>
<li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a></li>
<li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</a></li>
</ul>
<p><img src="/assets/images/tOSh/turbo-debugger-not-enough-memory.jpeg" alt="Turbo Debugger - Not Enough Memory" class="img-responsive" /><em>Turbo Debugger - Not Enough Memory</em></p>
<h1 id="stage-i---de-modo-real-a-modo-protegido">Stage I - De Modo Real a Modo Protegido</h1>
<p>Estamos en el <a href="https://wiki.osdev.org/Real_Mode">Real Mode</a>, lo último que hicimos fue saltar a otra posición de memoria donde deberíamos haber podido copiar otra parte de código binario
que llamaremos <strong>stage1</strong> que tomamos de un sector del Floppy donde estamos compilando y guardando todo
el bootloader como vimos en la primera parte de esta serie
y ahora toca ver que hacemos con el control de nuestra CPU. Esto me hace recordar algunas sextillas gauchescas de nuestro querido <strong>Diego Silicio</strong>,
gaucho del cátodo:</p>
<p>Atendiendo la obligada,<br />
no se haga el distraído<br />
no se olvide la pavada,<br />
ni se pierda el protegido,<br />
que es cuestión de buen formato,<br />
¡<em>memorí laiaut</em> bendito!<br /></p>
<p>Para poder entender mejor que es lo que vamos a hacer, y como vamos a configurar el CPU para que cambie de <strong>Modo</strong> tenemos que
entender nuestro <strong>Memory Layout (memorí laiaut)</strong> y donde estamos decidiendo <strong>casi arbitrariamente</strong>
–y esto es mucho muy importante, lo ‘casi’ arbitrario– donde vamos a copiar todo en memoria, este mismo <code class="language-plaintext highlighter-rouge">stage1.bin</code> el cual
cargamos del disco a memoria y saltamos y empezamos a correr con la última instrucción del bootloader: <code class="language-plaintext highlighter-rouge">jmp 0x9000</code>.</p>
<p>Primero cabe explicar que el Memory Layout de maquinas x86 ya está definido y hay direcciones de memoria que tienen
overlays con componentes y periféricos los cuales usamos para comunicarnos con ellos. Esto significa que no podemos
simplemente usar secciones de memoria ya que están siendo usadas por otros dispositivos y la CPU para comunicarse entre si,
el BIOS en <a href="https://web.archive.org/web/20221127214951/https://www.bioscentral.com/misc/bda.htm">0040:0000h</a> y también interfasear con el usuario.
Vamos a tomar como ejemplo la tabla que podemos encontrar en el documento <a href="https://files.osdev.org/mirrors/geezer/osd/ram/index.htm#layout">Physical Memory Layout of the PC</a> y mezclarla con
nuestro layout. La idea es en primer lugar <strong>evitar</strong> sobreescribir estas regiones de memoria que no nos pertenecen y
por el otro, encontrar un hueco donde nos podamos meter nosotros a escribir memoria para nuestro código para el <strong>stage1.bin</strong>
y en un futuro, nuestro <strong>kernel.bin</strong>. Deberíamos tener suficiente espacio para todo sin sobreescribir nada.</p>
<table>
<thead>
<tr>
<th>rango de memoria lineal</th>
<th>rango de direccion modo real</th>
<th>tipo de memoria</th>
<th>uso</th>
</tr>
</thead>
<tbody>
<tr>
<td>0- 3FF</td>
<td>0000:0000-0000:03FF</td>
<td>RAM</td>
<td>real-mode interrupt vector table (IVT)</td>
</tr>
<tr>
<td>400- 4FF</td>
<td>0040:0000-0040:00FF</td>
<td>RAM</td>
<td>BIOS data area (BDA)</td>
</tr>
<tr>
<td>500- 9FBFF</td>
<td>0050:0000-9000:FBFF</td>
<td>RAM</td>
<td>free conventional memory (below 1 meg)</td>
</tr>
<tr>
<td>0x9000 + (512bytes * 4 sectores)</td>
<td>0900:0000 – 0900:07FF</td>
<td>RAM (stage1.bin)</td>
<td>nos metemos por aca, debajo el primer mega de memoria</td>
</tr>
<tr>
<td>0x10000 + (512bytes * n sectores)</td>
<td>¡NOS CHUPA UN HUEVO!</td>
<td>RAM (kernel.bin)</td>
<td>a estas alturas, ya deberiamos estar en <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></td>
</tr>
<tr>
<td>0x80000</td>
<td>¡NOS CHUPA UN HUEVO!</td>
<td>RAM (stack)</td>
<td>El <strong>stack</strong> que va a ser usado por el kernel y nuestros programas</td>
</tr>
<tr>
<td>9FC00- 9FFFF</td>
<td>9000:FC00-9000:FFFF</td>
<td>RAM</td>
<td>extended BIOS data area (EBDA)</td>
</tr>
<tr>
<td>A0000- BFFFF</td>
<td>A000:0000-B000:FFFF</td>
<td>Video RAM</td>
<td>VGA framebuffers</td>
</tr>
<tr>
<td>C0000- C7FFF</td>
<td>C000:0000-C000:7FFF</td>
<td>ROM</td>
<td>video BIOS (32K is typical size)</td>
</tr>
<tr>
<td>C8000- EFFFF</td>
<td>C800:0000-E000:FFFF</td>
<td>NOTHING</td>
<td> </td>
</tr>
<tr>
<td>F0000- FFFFF</td>
<td>F000:0000-F000:FFFF</td>
<td>ROM</td>
<td>motherboard BIOS (64K is typical size)</td>
</tr>
<tr>
<td>100000- FEBFFFFF</td>
<td> </td>
<td>RAM</td>
<td>free extended memory (1 meg and above)</td>
</tr>
<tr>
<td>FEC00000- FFFFFFFF</td>
<td>various</td>
<td> </td>
<td>motherboard BIOS, PnP NVRAM, ACPI, etc.</td>
</tr>
</tbody>
</table>
<p>Teniendo todo esto en cuenta, podemos empezar a ver un poco de código, <code class="language-plaintext highlighter-rouge">stage1.asm</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; ----------------------------------------------------
; tOSh Operating System, Huh? (c) 2019
; stage1.asm by toshi
; $ nasm -D KERNEL_FIRST_SECTOR=$KERNEL_FIRST_SECTOR \
; -D KERNEL_SECTORS=$kernel_sectors \
; -f bin -l stage1.lst stage1.asm -o stage1.bin
; ----------------------------------------------------
bits 16
org 0x9000 ; we copied sector 2+ here!
[map symbols stage1.map] ; create a stage1.map file for offsets checking
KERNEL_OFFSET equ 0x10000
STACK_ADDRESS equ 0x80000
</code></pre></div></div>
<p>Todo lo que se ensamble va a ser en <code class="language-plaintext highlighter-rouge">16 bits</code> relativo a <code class="language-plaintext highlighter-rouge">org 0x9000</code>. Adicionalmente vamos a crear
un mapa de símbolos que nos va a ayudar a ver los offsets de las funciones en caso que los necesitemos
en el desarrollo con la directiva map symbols.</p>
<p>Entre varias ventajas, la que más útil me parece a mi es poder llevar el apunte de los <code class="language-plaintext highlighter-rouge">equ</code>,
los <strong>nombres de función</strong> y sus posiciones de memoria al haber sido ensambladas inclusive
cuando <code class="language-plaintext highlighter-rouge">stage1.asm</code> tiene varios <code class="language-plaintext highlighter-rouge">%include</code>.</p>
<p><code class="language-plaintext highlighter-rouge">map symbols stage1.map </code> produce un archivo que tiene la siguiente pinta:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- NASM Map file ---------------------------------------------------------------
Source file: stage1.asm
Output file: stage1.bin
-- Symbols --------------------------------------------------------------------
---- No Section ---------------------------------------------------------------
Value Name
00010000 KERNEL_OFFSET
00080000 STACK_ADDRESS
00000008 CODE_SEGMENT
00000010 DATA_SEGMENT
000B8000 VRAM
---- Section .text ------------------------------------------------------------
Real Virtual Name
9023 9023 prepare_protected_mode
903B 903B load_kernel
9052 9052 load_kernel.next_sector
9070 9070 load_kernel.destination_ok
908A 908A load_kernel.done
908F 908F load_error
909A 909A kernel_sectors_remaining
909C 909C error_hang
90A7 90A7 init_protected_mode
90E7 90E7 protected_mode_msg
9108 9108 kernel_load_fail_msg
9121 9121 kernel_load_msg
9148 9148 gdt_start
9150 9150 gdt_code_segment
9158 9158 gdt_data_segment
9160 9160 gdt_descriptor
9166 9166 gdt_end
9166 9166 enable_A20
9196 9196 a20wait
919D 919D a20wait2
91A4 91A4 asm_print_protected
91AA 91AA asm_print_protected.loop
91BD 91BD asm_print_protected_end
91BF 91BF asm_print_hex
91D0 91D0 asm_print_hex.loop
91E2 91E2 asm_print_hex.digit
91E5 91E5 asm_print_hex.write
</code></pre></div></div>
<p>Después de cambiar el fondo de pantalla a gris -podés ver esto en el código fuente del OS en la sección <a href="/resources/">Resources</a> -
hacemos una <code class="language-plaintext highlighter-rouge">call</code> a <strong>load_kernel</strong> que escencialmente lo que hace es copiar el binario del kernel (que todavía está en veremos)
a la posición de memoria arbitraria <code class="language-plaintext highlighter-rouge">0x10000</code>, definida en el <code class="language-plaintext highlighter-rouge">KERNEL_OFFSET equ 0x10000</code> más arriba.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>load_kernel:
; ---------------------------------------------------------
; Load KERNEL_SECTORS sectors from floppy into 0x10000
;
; Floppy geometry:
; 80 cylinders
; 2 heads
; 18 sectors per track
;
; Kernel starts at KERNEL_FIRST_SECTOR
; check build.sh for sector calculations
; ---------------------------------------------------------
push ax
push bx
push cx
push dx
; Destination = 0x10000 + n bytes, ES = 0x1000, BX = copy n bytes offset
xor bx, bx
mov ax, 0x1000
mov es, ax
</code></pre></div></div>
<p>Una cosa para destacar en este listado (phuá, "listado" le mandé, volvimos a los 80s!) es el cuidado que hay
que tener haciendo algo que voy a llamar ‘aritméticas de segmentos’ porque no se bien como llamarlo.
Cada vez que, en 16 bits, tenés que escribir algo en memoria, tenés que tener en cuenta siempre en el segmento
en el que vas a operar y como las instrucciones los usan o no y tener en cuenta la relacion que hay entre la dirección de
memoria "logica" y la <strong>física</strong> teniendo en mente siempre la siguiente fórmula:</p>
<blockquote>
<p>Dirección Física = (Segmento * 16) + Desplazamiento</p>
</blockquote>
<p>Todo esto tiene que ver con como poder direccionar más de 64 kilobytes (o sea, 2^16 son 16 bits)
cuando físicamente tenés uno, dos o más megabytes. Bueno, los muchachos de intel lo resolvieron asi y esto trae
bastante dolores de cabeza porque esta aritmética hace que las direcciones no sean únicas y se generen
solapamientos. La copia del Kernel a memoria la hago asi y después salto a <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a> donde este problema
desaparece y en el momento que estaba tratando de copiar un binario a memoria use las funciones del BIOS para copiar
del diskette a memoria el kernel, porque quería darle soporte a 286 que tiene un modo protegido falopa y después
se me complicó y quedó asi.</p>
<p><code class="language-plaintext highlighter-rouge">load_kernel</code> funciona y cuando logré darle la vuelta para copiar el kernel y que funcione, no lo quise tocar más.</p>
<p>Si entendés mas o menos que estoy haciendo exactamente en:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> xor bx, bx
mov ax, 0x1000
mov es, ax
</code></pre></div></div>
<p>donde estoy preparando la copia, vas a ver que en vez de pararme en 0x10000 que sería lo lógico,
me paro en <code class="language-plaintext highlighter-rouge"><--> Segmento:Desplazamiento <--> 0x1000:0x0000 <--> ES:BX</code>
estoy haciendo absuso de notación para que se "NOTE BIEN" lo que estoy diciendo porque esto es <strong>fundamental</strong> para entender
porque las cosas nunca caen en memoria donde uno espera.</p>
<p>O sea, por todo lo dicho:</p>
<blockquote>
<p>Memoria Fisica: 0x10000 (cinco ceros) <—> 1000:0000 (cuatroceros, cuatroceros)</p>
</blockquote>
<p>Y porque el segmento va en <code class="language-plaintext highlighter-rouge">ES</code>? cuando llamas a <code class="language-plaintext highlighter-rouge">INTerrupciones</code> o algunas <code class="language-plaintext highlighter-rouge">instrucciones</code> usan <code class="language-plaintext highlighter-rouge">SEGmentos</code> para operar.
Es convención y los detalles estan en los manuales de intel y <a href="http://www.ctyme.com/rbrown.htm">Ralf Brown’s Interrupt List</a>,</p>
<p>pues le digo, mi ingeniero<br />
que tiene la segmentación<br />
pa' que no falte ocasión<br />
y no se le pase el viaje<br />
a esos <strong>bites</strong> ni el anclaje<br />
ni una horrible excepción<br /></p>
<p>Lo que sigue en la función <code class="language-plaintext highlighter-rouge">load_kernel</code> es una rutina estándar de copia de de sectores del disquette a memoria:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ; Current CHS
xor ch, ch ; cylinder = 0
mov dh, 0 ; head = 0
mov cl, KERNEL_FIRST_SECTOR ; sector
; Number of sectors remaining
mov word [kernel_sectors_remaining], KERNEL_SECTORS
.next_sector:
cmp word [kernel_sectors_remaining], 0
je .done
; BIOS read: exactly ONE sector
mov ah, 0x02
mov al, 1
mov dl, 0 ; floppy drive A:
int 0x13
jc load_error
; ---------------------------------------------------------
; Advance destination by 512 bytes
; ---------------------------------------------------------
add bx, 512
; If BX wrapped around, advance ES by 0x1000
; because 0x1000:0000 -> 0x2000:0000
jnc .destination_ok
mov ax, es ; yuck!
add ax, 0x1000 ; we are going to leave all this
mov es, ax ; segment:offset madness very soon!
.destination_ok:
; One less sector to load
dec word [kernel_sectors_remaining]
; ---------------------------------------------------------
; Advance CHS
; ---------------------------------------------------------
inc cl ; next sector
cmp cl, 19 ; floppy has sectors 1..18
jb .next_sector
; End of track -> next head
mov cl, 1
inc dh
cmp dh, 2 ; heads 0 and 1
jb .next_sector
; End of cylinder -> next cylinder
mov dh, 0
inc ch
jmp .next_sector
.done:
pop dx
pop cx
pop bx
pop ax
ret
load_error:
; BIOS sets carry flag (CF) on error
; AX contains BIOS error information
mov ebx, kernel_load_fail_msg
call asm_print_protected
jmp error_hang
kernel_sectors_remaining dw 0
error_hang:
mov ebx, kernel_load_fail_msg
call asm_print_protected
jmp $ ; loop forever
; Instruction Pointer is self (JMP IP)
</code></pre></div></div>
<h2 id="preparando-el-modo-protegido">Preparando el Modo Protegido</h2>
<p>El <a href="https://wiki.osdev.org/Real_Mode">Real Mode</a> en el que estamos es una porquería de los 70's/80's y en pleno 2019 no deberíamos estar
escribiendo este tipo de código, asi que vamos a configurar el <code class="language-plaintext highlighter-rouge">CPU</code> para pasar de modo <em>16 bits</em> a modo <em>32 bits</em>.</p>
<p>Para esto, tenemos que hacer varias cosas:</p>
<ul>
<li>Armar un descriptor en la Global Descriptor Table o <a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a></li>
<li>Habilitar la línea del bus <a href="https://wiki.osdev.org/A20_Line">A20</a></li>
<li>Armar un Interrupt Descriptor Table <a href="http://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a> (Update: era un bardo mantenerlo en assembly, asi que lo pasé al Kernel en C y esto está en otro artículo)</li>
<li>Habilitar el <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a> después de configurar todo esto a través del registro de control <a href="https://wiki.osdev.org/CPU_Registers_x86">cr0</a></li>
<li>Saltar a código <strong>32 Bits</strong></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; Ok, we now try to switch to protected mode
prepare_protected_mode:
cli ; disable interrupts
lgdt [gdt_descriptor] ; load the gdt_descriptor table in gdt.asm
; with the lgdt (load GDT) instruction
; Fast A20 Gate 286+ ?
; using this Fast A20 gate resets the computer on 286
;in al, 0x92
;or al, 2
;out 0x92, al
call enable_A20 ; so we try to use this neat function instead
; lidt instruction execution moved to C kernel!
;lidt [idt_descriptor]
;xchg bx, bx ; magic bochs breakpoint
; protected mode 286+ ?
; we are NOT supporting 286!!!!!111 t_t
; tOSh it's 386+ -ONLY-
; lmsw ax ; pre-cr0 register is the 'msw' register in 286
; or ax, 0x1 ; and this way you enable
; smsw ax ; protected mode, but cannot make it work,
; don't know what I missed
mov eax, cr0
or eax, 0x1 ; set the 32-bit mode (Protected Mode)...
mov cr0, eax ; into cr0
; the CODE_SEGMENT is defined in gdt.asm
jmp CODE_SEGMENT:init_protected_mode ; far jump!
</code></pre></div></div>
<p>Lo que voy a explicar aca es una exagerada <em>superultrasobresimplificación</em> sobre lo que habría que leer
al menos dos volúmenes de los seis que componen los manuales de Intel donde se explican con lujo de detalles
todo lo necesario para entender como configurar el CPU.</p>
<h3 id="gdt">GDT</h3>
<p>La <a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a> es una estructura de datos usada por los procesadores Intel (32/64 bits) para definir segmentos de memoria y permisos para poder
escribir, leer y ejecutar en todas sus combinaciones posibles cada uno de esos segmentos. Es un array de 8 bytes
que tiene la siguiente pinta en [tOSh]:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; https://wiki.osdev.org/GDT_Tutorial
; https://wiki.osdev.org/Global_Descriptor_Table
bits 32
db 'GDT' ; GDT mark for bin
gdt_start:
; the first entry of the GDT must start with
; 8 null bytes
dd 0x0
dd 0x0
gdt_code_segment:
; this is the entry for the kernel address space
dw 0xFFFF ; limit 0:15 - 16 bits - 2 bytes
; (all will be pages of 4KiB, so take this into account)
dw 0x0000 ; base 0:15 - 16 bits - 2 bytes
db 0x00 ; base 16:23 - 8 bits - 1 byte
; Base address will be 0
db 10011010b ; access byte - 8 bits - 1 byte
; present bit: 1
; privilege, 2 bits, 0 = kernel space, 3 = userspace
; S: Descriptor type: enable for code/data segments, 0 for system segments
; executable bit: 1
; Direction bit/Conforming bit: 0, code only exec by priv lvl
; RW (code segments read access, data segments always read, set for writing) : 1
; Ac: accessed bit, set to 0 as recommended by osdev
db 11001111b ; first flags then limit 16:19 / (1 byte) 1 nibble - 1 nibble
; FLAGS (1st most significant nibble):
; Granularity: 0 = 1 byte blocks, 1= 4KiB blocks (pages)
; Size bit: 0 = 16 bit protected mode, 1 = 32 bit protected mode
; next to bytes in the nibble must be zero
; 2nd nibble: limit 16:19
;
db 00000000b ; base 24:31 - 8 bits - 1 byte
gdt_data_segment:
; this is the entry for the kernel address space
dw 0xffff ; limit 0:15 - 16 bits - 2 bytes
dw 0x0000 ; base 0:15 - 16 bits - 2 bytes
db 0x00 ; base 16:23 - 8 bits - 1 byte
db 10010010b ; access byte - 8 bits - 1 byte
db 11001111b ; first flags then limit 16:19 / (1 byte) 1 nibble - 1 nibble
db 00000000b ; base 24:31 - 8 bits - 1 byte
; gdt_tss_segment:
; ; this is the entry for the kernel address space
; dw 0x0000 ; limit 0:15 - 16 bits - 2 bytes
; dw 0x0000 ; base 0:15 - 16 bits - 2 bytes
; db 0x00 ; base 16:23 - 8 bits - 1 byte
; db 00000000b ; access byte - 8 bits - 1 byte
; db 00000000b ; first flags then limit 16:19 / (1 byte) 1 nibble - 1 nibble
; db 00000000b ; base 24:31 - 8 bits - 1 byte
; gdt_ldt_segment:
; ; this is the entry for the kernel address space
; dw 0x0000 ; limit 0:15 - 16 bits - 2 bytes
; dw 0x0000 ; base 0:15 - 16 bits - 2 bytes
; db 0x00 ; base 16:23 - 8 bits - 1 byte
; db 00000000b ; access byte - 8 bits - 1 byte
; db 00000000b ; first flags then limit 16:19 / (1 byte) 1 nibble - 1 nibble
; db 00000000b ; base 24:31 - 8 bits - 1 byte
; gdt_user_segment:
; ; this is the entry for the kernel address space
; dw 0x0000 ; limit 0:15 - 16 bits - 2 bytes
; dw 0x0000 ; base 0:15 - 16 bits - 2 bytes
; db 0x00 ; base 16:23 - 8 bits - 1 byte
; db 00000000b ; access byte - 8 bits - 1 byte
; db 00000000b ; first flags then limit 16:19 / (1 byte) 1 nibble - 1 nibble
; db 00000000b ; base 24:31 - 8 bits - 1 byte
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
gdt_end:
; constants to be used by stage1.asm
CODE_SEGMENT equ gdt_code_segment - gdt_start
DATA_SEGMENT equ gdt_data_segment - gdt_start
;TSS_SEGMENT equ gdt_tss_segment - gdt_start
;LDT_SEGMENT equ gdt_ldt_segment - gdt_start
;USER_SEGMENT equ gdt_user_segment - gdt_start
</code></pre></div></div>
<h3 id="a20">A20</h3>
<p>La línea <a href="https://wiki.osdev.org/A20_Line">A20</a> es una representación física del <strong>bit número 21</strong> de cualquier
dirección de memoria. Por una cuestión de compatibilidad con procesadores anteriores,
los diseñadores de los CPUs de Intel "la ataron con alambre". Hasta el dia de hoy,
en procesadores modernos basados en Intel, hay que seguir configurando esta chanchada que se hizo
hace como 50 años.</p>
<p>Hacele click a <a href="https://wiki.osdev.org/A20_Line">A20</a> y buscá en internet la historia completa de toda esa subtrama de diseño de procesadores
y de Intel que no tiene desperdicio.
Nosotros <strong>podríamos skippear poskippeamos todo el lore</strong> subiendo la vara de nuestro Sistema Operativo para computadoras PC Compatibles <strong>IBM PS/2+</strong>
usando el metodo mencionado en <strong>osdev: Fast A20 Gate</strong>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>in al, 0x92
or al, 2
out 0x92, al
</code></pre></div></div>
<p>Pero mejor incluimos y llamamos un <code class="language-plaintext highlighter-rouge">a20.asm</code> que copié y pegué de algún lugar de la mancha el cual no puedo acordarme.</p>
<h3 id="cr0">cr0</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mov eax, cr0
or eax, 0x1 ; set the 32-bit mode (Protected Mode)...
mov cr0, eax ; into cr0
; the CODE_SEGMENT is defined in gdt.asm
jmp CODE_SEGMENT:init_protected_mode ; far jump!
</code></pre></div></div>
<p>Setiamos con un <code class="language-plaintext highlighter-rouge">OR</code> el registro de control <a href="https://wiki.osdev.org/CPU_Registers_x86">cr0</a> el bit 1 para habilitar el <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a> y saltamos directamente
al código 32 bits.</p>
<p>Hay banda de cosas más que se pueden configurar en el CPU pero dejamos las otras funcionalidades y configuraciones disponibles
como ejercicio para el lector ;-).</p>
<h1 id="un-mundo-de-32-bits">Un Mundo de 32 Bits</h1>
<p>Llegamos (¡finalmente!) al mundo de los 32 bits y el <em>realm</em> de las direcciones de memoria lineal.
Copiamos un <strong>"kernel misterioso hecho en C"</strong> a memoria pero antes de poder usarlo, tenemos que
<strong>obviamente</strong> seguir configurando el CPU, sus segmentos en el modo 32 bits y además el <a href="https://wiki.osdev.org/Stack">stack</a>.</p>
<p>pHUN pHAKT: Al <a href="https://wiki.osdev.org/Stack">stack</a> en criollo le decimos <strong>"La Pila"</strong>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; ---------------------------------------------------------------------
; 32 bit code! now we are able to use 32 bit instructions at this point
; ---------------------------------------------------------------------
bits 32
init_protected_mode:
; and we need to setup the segments again
; with our previously configured GDT configuration addresses
; find gdt.asm and other %includes further in this listing
mov ax, DATA_SEGMENT ; defined in gdt.asm
mov ds, ax ; data segment
mov es, ax ; extended segment
mov fs, ax ; fuck you segment
mov ss, ax ; stack segment
;mov ax, USER_SEGMENT ; in the future, maybe...
mov gs, ax ; gorgeous segment <3
;mov ax, CODE_SEGMENT
;mov cs, ax ; TODO: why i didn't setup the code segment?
; while reviewing the code for the article
; I found this commented like years ago
; and I got startled. Maybe for another
; time...
;ret ; !!!!!
; set up the stack
;mov ebp, 0xf90000 ; 16MB RAM machine! this breaks old 86box / PCem Machines!
mov ebp, STACK_ADDRESS ; equ 0x80000 --> stack ~ 576KiB
mov esp, ebp
</code></pre></div></div>
<p>Una de las grandes sesiones de debug que tuve que <strong>spawnearon</strong> pedazos de <strong>ISABugger-Related-Code</strong>
fue entender porque volaba por los aires nuestro kernel y era exactamente setear el <a href="https://wiki.osdev.org/Stack">stack</a> correctamente.
Como se puede apreciar en el <code class="language-plaintext highlighter-rouge">asm</code>, estaba usando la dirección <code class="language-plaintext highlighter-rouge">0xf90000</code> como <code class="language-plaintext highlighter-rouge">extended base pointer</code>
y <code class="language-plaintext highlighter-rouge">extended stack pointer</code> para definir donde iba a vivir nuestro stack y en las VMs que estaba probando,
no tenia TANTA memoria, haciendo que me vaya fuera de los boundaries y el CPU me tire una excepción
irrecuperable. Solo una instrucción y bastante tiempo de debugging con <code class="language-plaintext highlighter-rouge">bochs</code> me hicieron dar cuenta del error.
Entonces, el <a href="https://wiki.osdev.org/Stack">stack</a> ahora vive aproximadamente alrededor de los <strong>576KiB</strong>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ; [ a lot of debugging stuff ]
; PIC Remapping ! (now handled by kernel at pic.c)
; mov al, 0x11
; out 0x20, al ; restart pic1
; out 0xa0, al ; restart pic2
; mov al, 0x20
; out 0x21, al ; pic1 now starts at 32
; mov al, 0x28
; out 0xa1, al ; pic2 now starts at 40
; mov al, 0x04
; out 0x21, al ; setup cascading
; mov al, 0x02
; out 0xa1, al
; mov al, 0x01
; out 0x21, al
; out 0xa1, al ; listo!
; ; IRQ1 = keyboard
; mov al, 0xFD
; out 0x21, al
; ; mask slave PIC
; mov al, 0xFF
; out 0xA1, al
; the kernel is the responsible for enabling
; the interrupts!
; the kernel now initializes IDT --> THEN STI()
;sti ; enable interrupts!
jmp KERNEL_OFFSET ; execute kernel code
</code></pre></div></div>
<p>Y el último <code class="language-plaintext highlighter-rouge">jmp</code>. Todo lo demás fue comentado porque lo pasé al <code class="language-plaintext highlighter-rouge">kernel</code> hecho en C el cual vamos a saltar, el próximo artículo.
Recapitulando, prácticamente lo que hicimos fue <strong>copiar el kernel</strong> que todavía no tenemos, saltar a <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a>, una vez
ahí, preparar el <a href="https://wiki.osdev.org/Stack">stack</a> en 32 bits y finalmente saltar al <strong>Kernel C Entrypoint</strong>.</p>
<h1 id="referencias">Referencias</h1>
<ul>
<li><a href="https://wiki.osdev.org/Boot_Sequence">Boot_Sequence</a></li>
<li><a href="https://wiki.osdev.org/Real_Mode">Real Mode</a></li>
<li><a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></li>
<li><a href="http://www.ctyme.com/rbrown.htm">Ralf Brown’s Interrupt List</a></li>
<li><a href="https://wiki.osdev.org/Calling_Conventions">calling convention</a></li>
<li><a href="https://wiki.osdev.org/MBR_%28x86%29">MBR</a></li>
<li><a href="https://files.osdev.org/mirrors/geezer/osd/ram/index.htm#layout">Physical Memory Layout of the PC</a></li>
<li><a href="https://stanislavs.org/helppc/bios_data_area.html">BIOS Data Area</a></li>
<li><a href="https://web.archive.org/web/20221127214951/https://www.bioscentral.com/misc/bda.htm">0040:0000h</a></li>
<li><a href="https://wiki.osdev.org/Global_Descriptor_Table">GDT</a></li>
<li><a href="https://wiki.osdev.org/A20_Line">A20</a></li>
<li><a href="http://wiki.osdev.org/Interrupt_Descriptor_Table">IDT</a></li>
<li><a href="https://wiki.osdev.org/CPU_Registers_x86">cr0</a></li>
<li><a href="https://wiki.osdev.org/Stack">stack</a></li>
</ul>
tOSh para x86 - i386+ | Parte I | Bootloader2019-12-02T14:08:01+00:00http://example.com/codeando-un-bootloader<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.</p>
<h1 id="partes">Partes</h1>
<p>Esta serie de artículos va a tener las siguientes partes:</p>
<ul>
<li><a href="/codeando-un-bootloader">Parte I - Bootloader</a> <strong><— Estás acá</strong></li>
<li><a href="/modo-protegido">Parte II - Stage 1</a></li>
<li><a href="/saltando-a-c">Parte III - Saltando a C</a></li>
<li><a href="/interrupciones">Parte IV - Interrupciones</a></li>
<li><a href="/sistema-de-archivos-y-disquetera">Parte V - Sistema de Archivos y Driver de Disquetera</a></li>
<li><a href="/tOSh-corriendo-en-maquinas">Parte VI - Booteando en diferentes tipos de máquinas</a></li>
</ul>
<p><img src="/assets/images/tOSh/isa-post-tester-card.jpeg" alt="ISA POST Tester Card" class="img-responsive" /><em>ISA POST Tester Card</em></p>
<h1 id="prefacio">Prefacio</h1>
<p><strong>¿Otro Sistema Operativo más?</strong> Si. ¿Por qué? Porque puedo. ¡Increíblemente tengo tiempo libre! Y como esto forma parte de un proyecto más grande,
en unos meses cuando vuelva a tener el tiempo suficiente como para poder revisarlo,
voy a necesitar tener un resumen y documentación y escribir este artículo de alguna
manera me va a ayudar a tener todo en un solo lugar para consultarlo luego.
¿Cuál es ese proyecto? ¿Vas a competirle al <code class="language-plaintext highlighter-rouge">Kernel</code> de Linux? ¿Vas a tratar de reemplazar Windows por el tuyo? No. Para nada.
De hecho, creo que es bastante difícil hacer algo así en estos días individualmente y todo lo que uno pueda llegar a hacer en
solitario no podría llegar más lejos que lo que <a href="https://en.wikipedia.org/wiki/Terry_A._Davis">Terry A. Davis</a> haya logrado con <a href="https://en.wikipedia.org/wiki/TempleOS">Temple OS</a> que es de por si, cuando menos impresionante.</p>
<p>Sinceramente, el objetivo de este <strong>Sistema Operativo</strong> es demostrar mis habilidades como programador, el entendimiento
sobre sistemas operativos, arquitecturas, lenguajes (ASM, C), la creación de herramientas "en el andar"
y una ristra de cosas más para un poco cancherearle a mis amigos y otro poco para chapearle a recruiters y futuros empleadores,
en caso que tengan dudas de mis habilidades.</p>
<blockquote>
<p><strong>En caso de que seas un Recruiter: ¡Hola, mucho gusto!, mandale por favor esta serie de artículos al lider técnico pertinente</strong>, le va a encantar.</p>
</blockquote>
<blockquote>
<p><strong>En caso que seas el "Lider Técnico Pertinente": ¡Hola, ¿que tal? Encantado: ¡Mandame un mail por favor y charlemos!</strong></p>
</blockquote>
<p>Si no entrás en alguna de estas dos categorías, quizá a vos también te sirvan esta serie de artículos por vaya a saber que motivo.
<strong>¿Universidad? ¿Curiosidad? ¿Masoquismo?</strong> Da igual, bienvenido y disfrutalos tanto como yo disfruté y sufrí escribir sobre como hacer
un <strong>Sistema Operativo para la Arquitectura Intel</strong>.</p>
<h1 id="descarga-de-tosh-img--código-fuente">Descarga de tOSh (img / código fuente)</h1>
<p>Para descargar el Sistema Operativo <a href="/resources/">tOSh</a>, podés hacerlo desde la sección <a href="/resources/">Resources</a> de este mismo sitio.
En la tabla de recursos hay varias secciones, y en <strong>Software</strong> está la imagen ya compilada del sistema operativo y
en la sección <strong>Sourcecode</strong> la última versión de su <strong>código fuente</strong>.
<strong>¡¡¡Bajátelo YA NO TE LO PIERDAS!!! (?)</strong></p>
<h1 id="por-qué-se-llama-tosh">¿Por qué se llama tOSh?</h1>
<p>Tosh, tsh o Toshi son algunos de mis handles. Quise hacer algo parecido al acrónimo recursivo <strong>GNU is Not Unix</strong> y me quedó:</p>
<p><strong>"tOSh Operating System, Huh?"</strong></p>
<p>En algún momento me pareció gracioso. Hoy no tanto y me dio paja cambiar el nombre del repo ;D
Bueno, basta de introducciones y arranquemos enseguida por lo primero de lo primerísimo que hay que tener en cuenta
para hacer un Sistema Operativo para Intel, entender su <strong>Secuencia de Booteo</strong>.</p>
<h1 id="secuencia-de-booteo-en-intel">Secuencia de Booteo en Intel</h1>
<p>La <code class="language-plaintext highlighter-rouge">Secuencia de booteo</code> ó <a href="https://wiki.osdev.org/Boot_Sequence">Boot_Sequence</a> consiste en un conjunto de pasos que necesitan ser ejecutados en cierto orden para poder cargar
al final, un <code class="language-plaintext highlighter-rouge">kernel</code> en memoria y eventualmente saltar a su código para otorgarle control a la administación los dispositivos de la computadora,
interfaz de usuario, ejecución de programas, etc…
Vamos a listar muy brevemente estos pasos prestando suma atención a cosas que necesitamos saber sobre como se configura el equipo desde que
apretás <code class="language-plaintext highlighter-rouge">power-on</code> en tu PC hasta que puedas insertar algunas instrucciones cuando llegás a la instancia de poder ejecutar tu propio código.</p>
<h2 id="post">P.O.S.T.</h2>
<p>Cuando una computadora se enciende, la mayor parte de los <code class="language-plaintext highlighter-rouge">registros</code> del <code class="language-plaintext highlighter-rouge">CPU</code> tienen valores <code class="language-plaintext highlighter-rouge">muy bien definidos</code>, incluyendo
el <code class="language-plaintext highlighter-rouge">puntero de instrucción (IP)</code> o como en otras arquitecturas o libros de texto lo llaman <code class="language-plaintext highlighter-rouge">Program Counter</code>. El valor de <code class="language-plaintext highlighter-rouge">IP</code> apenas
se enciende la computadora contiene la dirección de memoria que ejecuta la <code class="language-plaintext highlighter-rouge">CPU</code>. Esta dirección de memoria se llama <code class="language-plaintext highlighter-rouge">reset vector</code>
y está <code class="language-plaintext highlighter-rouge">hardcodeado</code> en el CPU: la dirección de memoria es particularmente <code class="language-plaintext highlighter-rouge">0xFFFFFFF0</code>. El <code class="language-plaintext highlighter-rouge">motherboard</code> de la computadora se
asegura que la instrucción que se ejecute en el <code class="language-plaintext highlighter-rouge">reset vector</code> sea una instrucción <code class="language-plaintext highlighter-rouge">jump</code> a la dirección de memoria mapeada al
<code class="language-plaintext highlighter-rouge">entry point</code> de la <code class="language-plaintext highlighter-rouge">BIOS</code>. Toda esta ensalada de direcciones de memoria del BIOS están organizadas por el <code class="language-plaintext highlighter-rouge">chipset</code> del motherboard
porque en este momento, la <code class="language-plaintext highlighter-rouge">RAM</code> contiene basura random.
Finalmente, la <code class="language-plaintext highlighter-rouge">CPU</code> ejecuta el código del firmware. En un sistema con BIOS legacy, el BIOS inicializa parte del hardware de la computadora
y realiza el <code class="language-plaintext highlighter-rouge">POST - Power-On-Self-Test</code>, que consiste en
una serie de diagnósticos de algunos componentes esenciales como la placa de video. Por ejemplo, si la placa de video falla en esta instancia, el código
del POST generalmente causa que el BIOS emita un par de pitidos en el <code class="language-plaintext highlighter-rouge">speaker</code> de la computadora para que vos puedas agarrar el manual,
y dependiendo del número de pitidos, saber por qué está fallando la inicialización, y esta información generalmente está en el manual
del motherboard para consultar que significan 3 pitidos largos y uno corto, 2 pitidos cortos y uno largo, etc…</p>
<h2 id="después-del-post-saltar-al-vacío">Después del P.O.S.T.: Saltar al vacío</h2>
<p>Una vez que se terminó de chequear los componentes esenciales, el <code class="language-plaintext highlighter-rouge">BIOS</code> va a querer <code class="language-plaintext highlighter-rouge">bootear</code> un <code class="language-plaintext highlighter-rouge">sistema operativo</code>. Y este sistema operativo
debería poder encontrarse en algún lado. Entonces empieza a buscar en discos rígidos, USB sticks, disquettes o CD-ROMs. Si el BIOS no encuentra
ningún dispositivo disponible, se detiene la ejecución del CPU y no sin antes mostrar algun texto acorde a lo que sucedió:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Non-System Disk or Disk Error.
</code></pre></div></div>
<p>Sin embargo, si encuentra algún dispositivo, el <code class="language-plaintext highlighter-rouge">BIOS</code> va a tratar de leer el primer <code class="language-plaintext highlighter-rouge">sector</code> de <code class="language-plaintext highlighter-rouge">512 bytes</code> en el dispositivo de almacenamiento
que haya encontrado primero (o esté configurado en el BIOS, claro).
Este primer sector, es llamado coloquialmente <a href="https://wiki.osdev.org/MBR_%28x86%29">MBR</a> <code class="language-plaintext highlighter-rouge">(Master Boot Record)</code>. El <code class="language-plaintext highlighter-rouge">BIOS</code> <em>carga cualquier cosa que haya en este primer sector</em>
a la dirección de memoria <code class="language-plaintext highlighter-rouge">0x0000:0x7c00</code> (segmento 0, dirección 0x7c00) y salta a esa dirección sea cual fuere el código que haya para ejecutar.
Un salto de fe. (!)</p>
<h2 id="sólo-512-bytes-disponibles">Sólo 512 bytes disponibles</h2>
<p>Bueno, no tenés exactamente 512 bytes disponibles para escribir el código del bootloader si no un poco menos.
La BIOS busca en los "dispositivos booteables" una marca o <code class="language-plaintext highlighter-rouge">magic number</code> (si) que tiene que ser exactamente la secuencia de bytes <code class="language-plaintext highlighter-rouge">0x55</code> y
<code class="language-plaintext highlighter-rouge">0xAA</code> en los offsets <code class="language-plaintext highlighter-rouge">510</code> y <code class="language-plaintext highlighter-rouge">511</code> respectivamente.
Ni hablar que estos 510 bytes disponibles son desde un <code class="language-plaintext highlighter-rouge">disquette</code>. Si necesitás bootear desde un disco rígido, entonces
tu espacio se reduce a <code class="language-plaintext highlighter-rouge">446 bytes</code>.
Para colmo, en todo este reducido espacio necesitás hacer las siguientes cosas para tener un bootloader decente:</p>
<ul>
<li>Determinar desde cual partición bootear</li>
<li>Saber donde está la imagen de tu <code class="language-plaintext highlighter-rouge">kernel</code> en la partición de booteo</li>
<li>Cargar la imagen del <code class="language-plaintext highlighter-rouge">kernel</code> en memoria (El <code class="language-plaintext highlighter-rouge">BIOS</code> tiene funciones para leer sectores y copiar a memoria)</li>
<li>Pasar de <code class="language-plaintext highlighter-rouge">Modo Real</code> a <code class="language-plaintext highlighter-rouge">Modo Protegido</code> (Estamos en <code class="language-plaintext highlighter-rouge">Modo Real</code> con un máximo de <code class="language-plaintext highlighter-rouge">1 MB</code> disponible, aunque nuestra computadora tenga over 9000 GB)</li>
</ul>
<p>El <code class="language-plaintext highlighter-rouge">Modo Protegido</code> es entre otras cosas, saltar de instrucciones y modo de direcciónamiento de <code class="language-plaintext highlighter-rouge">16 bits</code> a <code class="language-plaintext highlighter-rouge">32 bits</code>, lo que nos permite dejar de
tener una limitación de acceso a <code class="language-plaintext highlighter-rouge">1 Megabyte</code> de memoria, ampliando el espacio de direcciónamiento hasta <code class="language-plaintext highlighter-rouge">4 Gigabytes</code></p>
<ul>
<li>Setear el <code class="language-plaintext highlighter-rouge">stack</code> (sí, necesitamos configurar nosotros mismos el stack)</li>
<li>Mensajes de error! (el bootloader no encuentra la imagen? no se pudo copiar el kernel a memoria? que esta pasando?)</li>
</ul>
<p>Hay formas de resolver estos problemas, ya que de otra manera, no existirían los sistemas operativos actuales. Estos
problemas a lo largo de los últimos 40 años en la historia de las <code class="language-plaintext highlighter-rouge">PC</code> se resolvieron y discutieron una y otra vez, como
estamos haciendo acá también. Y se van a seguir discutiendo y escribiendo artículos sobre este tema.</p>
<h1 id="escribiendo-un-bootloader">Escribiendo un Bootloader</h1>
<p>La información anterior es la mínima indispensable para poder entender porque algunos valores que parecen totalmente
arbitrarios no lo son. A continuación vamos a escribir un <code class="language-plaintext highlighter-rouge">bootloader</code> de <code class="language-plaintext highlighter-rouge">512 bytes</code> que nos permita <em>al menos</em>
copiar <code class="language-plaintext highlighter-rouge">sectores</code> de un dispositivo a memoria y saltar a esa dirección para continuar.
Esto se lo llama <code class="language-plaintext highlighter-rouge">One-Stage loading</code>: Nuestro <code class="language-plaintext highlighter-rouge">bootloader</code> carga una imagen del kernel (que debe de ser menor a <em>1 Megabyte</em>),
saltamos a un <code class="language-plaintext highlighter-rouge">stub</code> con el que tenemos que cambiar de <code class="language-plaintext highlighter-rouge">Modo Real</code> a <code class="language-plaintext highlighter-rouge">Modo Protegido</code> para eventualmente saltar apropiadamente
a nuestro <code class="language-plaintext highlighter-rouge">kernel</code>.</p>
<h2 id="espera-antes-de-empezar">¡Espera! Antes de empezar…</h2>
<p>Vamos a instalar y a hacer los siguientes pasos:</p>
<ul>
<li>Usar cualquier distribución de <code class="language-plaintext highlighter-rouge">Linux</code></li>
<li>Instalar <code class="language-plaintext highlighter-rouge">nasm</code> (compilador de assembly)</li>
<li>Instalar <code class="language-plaintext highlighter-rouge">qemu</code> (emulador de varias arquitecturas, nosotros vamos a emular la arquitectura 386+ de Intel)</li>
<li>Instalar <code class="language-plaintext highlighter-rouge">PCem</code> (emulador de componentes de PC, emula diferentes tipos de BIOSes, motherboards, CPUs, placas de video, etc…)</li>
<li>Instalar <code class="language-plaintext highlighter-rouge">bochs</code> un emulador que te va a hacer compania durante todo el desarrollo del sistema operativo, ya que te permite
leer registros especiales como GDT, IDT, CRn. Es un emulador que te permite <strong>debuggear</strong>, muy parecido de <strong>gdb</strong>.</li>
</ul>
<h2 id="-deberás-aprobar-un-examen-de-historia">… deberás aprobar un examen de historia.</h2>
<p>Antes de seguir, una aclaración importante: todo lo que estamos haciendo acá describe el flujo clásico de <strong>BIOS legacy</strong>.
Las máquinas modernas también pueden bootear usando UEFI, cuyo mecanismo de carga es distinto.
En esta serie vamos a quedarnos deliberadamente con BIOS porque estamos construyendo un bootloader x86 clásico.</p>
<p>Vamos a crear 3 archivos:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">bootsector.asm</code></li>
<li><code class="language-plaintext highlighter-rouge">build_and_run.sh</code></li>
<li><code class="language-plaintext highlighter-rouge">stage1.asm</code></li>
</ul>
<p>Los tres vacios, salvo <code class="language-plaintext highlighter-rouge">build_and_run.sh</code> que más o menos, por conveniencia, debería tener el siguiente contenido:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
nasm -f bin bootsector.asm -o boot.bin
nasm -f bin stage1.asm -o stage1.bin
cat boot.bin stage1.bin > tOSh.bin
qemu-system-x86_64 -fda tOSh.bin
</code></pre></div></div>
<p>Primero que nada, configuramos <strong>nasm</strong> para que ensamble nuestro código assembly exactamente como queremos.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; ------------------------------------------------
; tOSh Operating System, Huh? (c) 2019
; bootsector.asm by toshi
; $ nasm -f bin bootsector.asm -o boot.bin
; ------------------------------------------------
;
; docs
; https://wiki.osdev.org/Boot_Sequence
; https://wiki.osdev.org/Rolling_Your_Own_Bootloader
; https://stackoverflow.com/questions/34178717/load-segment-from-floppy-with-int13h
bits 16 ; setup nasm to 16 bit code
cpu 186 ; assemble with 80186 instruction set (ie.: push word label)
org 0x7c00 ; all offsets will be at 0x7c00
; after here we will be able to execute code
; in x86 real mode, so these are the first instructions
section .text
global _start ; make this symbol global so we can reference
; it later in a custom linker script
_start:
jmp boot
</code></pre></div></div>
<p>La primer instrucción de todas es <code class="language-plaintext highlighter-rouge">jmp boot</code> que va a un <strong>label</strong> más adelante.
Para que <strong>nasm</strong> sepa exactamente hacia donde tiene que saltar (jmp boot) tenemos que
forzar al ensamblador a que no tome decisiones por su cuenta y trate de usar lo último de lo último
que tenga disponible.
Por esto, seteamos a nasm que escriba código <code class="language-plaintext highlighter-rouge">16 bits</code>, que use hasta el máximo <strong>set de instrucciones 80186</strong>
con la directiva <code class="language-plaintext highlighter-rouge">cpu</code> y finalmente con <code class="language-plaintext highlighter-rouge">org 0x7c00</code> le decimos que todos los "ceros" de los offsets de los
jumps, labels y demás empiezan en 0x7c00: es decir que nuestro "nuevo cero" relativo es lo que configuremos con <code class="language-plaintext highlighter-rouge">org</code>.</p>
<p>Esto es sumamente importante y es un concepto que mejor que lo entiendas ahora que después cuando sea demasiado tarde
y que cuando tengas a veces que leer código ensamblado, las instrucciones carezcan de total sentido.
Cuando saltás (jmp, jx, jxx) los saltos pueden ser <code class="language-plaintext highlighter-rouge">far</code> o <code class="language-plaintext highlighter-rouge">near</code>. Para un salto <code class="language-plaintext highlighter-rouge">far</code> en x86 16 bits, se tienen
en cuenta el segmento y el offset de los **modos de direcciónamiento de memoria de intel 16 bits o <a href="https://wiki.osdev.org/Real_Mode">Real Mode</a>.</p>
<p>Uno de nuestros objetivos más importantes después de lograr bootear el CPU y copiar el Kernel en memoria va a ser
salir del <a href="https://wiki.osdev.org/Real_Mode">Real Mode</a> y saltar al <a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a>. Es infinitamente más fácil trabajar con modos de direcciónamiento
de memoria del modo protegido que los del modo real, porque el juego del cálculo de la memoria física entre segmento y offset
es confuso y molesto. En cambio, en modo protegido, directamente vas a una posición de memoria de 32bits y listo.</p>
<h2 id="pulsa-enter-para-continuar">Pulsa ENTER para continuar…</h2>
<p>Continuamos con algunas funciones útiles para hacer que nuestra secuencia de booteo sea la más hermosa del barrio:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; http://www.ctyme.com/intr/rb-0097.htm
; ralf brown scroll down window int10h
clrscr:
push bp
mov bp, sp
mov ah, 0x07 ; set "scroll down window" for int 10h
mov al, 0 ; clear entire window with null char
; https://en.wikipedia.org/wiki/BIOS_color_attributes
mov bh, 0x4f ; colors (nibbles background/foreground)
; we choose red background, white characters
mov cx, 0 ; top left screen (0,0) (cx register)
mov dh, 24 ; 25 rows (from 0 to n-1) (dx register)
mov dl, 79 ; 80 cols (from 0 to n-1)
int 0x10
mov sp, bp
pop bp
ret
sleep:
push ax
push cx
push dx
mov ah, 0x86 ; int 15h, function 86h, CX:DX interval in microseconds
mov cx, 0x20 ; just a few seconds
mov dx, 0x0000
int 0x15
pop dx
pop cx
pop ax
ret
; print(char *msg_string)
print:
push bp
mov bp, sp
; move the cursor
; Set cursor position AH=02h BH = Page Number, DH = Row, DL = Column
mov dh, 12 ; row
mov dl, 16 ; col
mov bh, 0 ; page 0
mov ah, 0x2 ; move cursor service
int 0x10
; as we have stack, we grab the parameter
; of print
mov si, [bp+4] ; move the ptr of the first
; argument
mov ah, 0x0e ; write char teletype style
; http://www.ctyme.com/intr/rb-0106.htm
mov bh, 0 ; page number 0
mov bl, 0 ; foreground (only graphics mode)
.loop:
lodsb ; load a byte from ds:si into al
; glad we setup ds register before,
cmp al, 0 ; is the null caracter?
je .print_end
int 0x10 ; BIOS Video services
jmp .loop
.print_end:
mov sp, bp
pop bp
ret
load_disk:
push dx
mov ah, 0x02 ; int 13h, read
mov al, dh ; number of sectors to read
mov cl, 0x02 ; cl, sector, 0x01 is the boot sector
; 0x02 is where we will put our
; 'stage1' data
mov ch, 0x00 ; ch, cylinder
; boot_drive_numbers -->
; drive number, 0 = floppy1, 1 = floppy2
; 0x80 = hdd1, 0x81 = hdd2
mov dl, [boot_drive_number] ; the BIOS should have told us what's the
; drive number that we grabbed in the very first
; instructions
mov dh, 0x00 ; dh, head number (0 to F)
int 0x13 ; BIOS interruption
jc load_error ; if carry bit set, then error
pop dx
cmp al, dh ; al is now num of sectors read
jne sectors_error
ret
load_error:
push word load_error_msg
call print
add sp, 2
jmp error_hang
sectors_error:
push word sectors_error_msg
call print
add sp, 2
jmp error_hang
error_hang:
jmp $
</code></pre></div></div>
<p>En todas las funciones que siguen a continuación,
como no tengo nada de nada, salvo la parte baja de la memoria, estar en <strong>Modo Real</strong> y la posibilidad de hacer llamadas de interrupción al <code class="language-plaintext highlighter-rouge">BIOS</code>,
la única interfaz para poder hacer algo es la <code class="language-plaintext highlighter-rouge">BIOS</code>. Para poder obtener una lista exhaustiva de las interrupciones al <code class="language-plaintext highlighter-rouge">BIOS</code> y cada una de las
funciones que podés usar para interactuar con otros dispositivos sea la disquetera, el teclado o la pantalla es la
vieja y quierida <a href="http://www.ctyme.com/rbrown.htm">Ralf Brown’s Interrupt List</a>, de valor incalculable.</p>
<p><code class="language-plaintext highlighter-rouge">clrscr:</code> hace exactamente lo que promete su acrónimo: <strong>limpiar la pantalla</strong>.</p>
<p><code class="language-plaintext highlighter-rouge">sleep:</code> detener la ejecución por unos segundos.</p>
<p><code class="language-plaintext highlighter-rouge">print:</code> esta función imprime un texto que esté apuntado por el puntero como parámetro en el <code class="language-plaintext highlighter-rouge">stack</code></p>
<p><code class="language-plaintext highlighter-rouge">load_disk:</code> cargar exactamente el (head) cabezal 0x00, (cylinder) cilindro 0x00, sector 0x02 que es donde va a
estar el código del <strong>stage1</strong> que vendrá mucho más tarde.</p>
<p><code class="language-plaintext highlighter-rouge">load_error, sectors_error y error_hang</code>: funciones que imprimen el tipo de error en pantalla y terminan colgando la CPU a propósito con un loop infinito con <code class="language-plaintext highlighter-rouge">jmp $</code></p>
<p>Ahora lo que sigue es el código de booteo que viene debajo con <code class="language-plaintext highlighter-rouge">boot:</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>boot:
nop ; stylish nop is stylish
; save DL register setup by BIOS for getting the drive number
mov [boot_drive_number], dl
</code></pre></div></div>
<p>Esencialmente, lo primero que hicimos, si seguís atentamente el código, es saltar al label <code class="language-plaintext highlighter-rouge">boot:</code>,<br />
y después de el simpático y elegante <code class="language-plaintext highlighter-rouge">nop</code> que puse ahi solamente por ponerlo porque pintó,
lo primerísimo que hago es guardar el valor del registro <code class="language-plaintext highlighter-rouge">dl</code> en otro label <code class="language-plaintext highlighter-rouge">boot_drive_number</code>.
Cuando booteamos, el BIOS llena algunos registros con datos, y en <code class="language-plaintext highlighter-rouge">dl</code>, el BIOS nos guardó el número
del drive en el cual vamos a bootear.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mov ax, 0x0000 ; if ds is 0x7c0 the ORG directive must be disabled
; but OFC ds is 0, then ORG directive must be enabled!
; TODO: Why? --
; BECAUSE: every memory position, or
; memory reference implicitely has
; ds inside -> [ptr] is [ds:ptr]
; that's why
; ALSO: segmentation essentialy is
; << 4, so 0x7c0 << 4 is 0x7c00
mov ds, ax ; set the data segment register
; we set ds as 0x0 because we use ORG 0x7c00
; for our offsets assembled in the binary
mov es, ax ; and 'extended?' register too
; setup the stack
mov ax, 0x7c00 + 512 ; 512 is the total size of the boot sector
mov ss, ax ; so we set the stack segment
; of this code, TODO: explain segment addresses
; arithmetics
mov sp, 0x1000 ; let's reserve 4k of stack
</code></pre></div></div>
<p>Acá seteamos un stack rudimentario para poder hacer uso de nuestras propias funciones en el bootloader.
¿Lo necesitamos? y la verdad que no, pero si voy a estar booteando también me gustaría andar mostrando
mensajes en la pantalla –para debuggear o por simple facha nomás– y la forma más fácil de hacerlo es
creando "funciones" que necesitan un <strong>stack</strong> para poder pasar parámetros.</p>
<p>Entonces seteamos a 0x0000 nuestros registros de segmento <code class="language-plaintext highlighter-rouge">ds</code> (data segment) y <code class="language-plaintext highlighter-rouge">es</code> (extended segment) y
configuramos el registro de segmento <code class="language-plaintext highlighter-rouge">ss</code> (stack segment) con una dirección de memoria que está al final de
nuestro código.</p>
<p>Finalmente con la instrucción <code class="language-plaintext highlighter-rouge">mov sp, 0x1000</code> lo que estamos haciendo es reservar 4 kilobytes del stack asignando
<code class="language-plaintext highlighter-rouge">0x1000</code> al registro <strong>stack pointer</strong> <code class="language-plaintext highlighter-rouge">sp</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> call clrscr
push word loading_msg
call print
add sp, 2
call sleep
</code></pre></div></div>
<p>Habiendo configurado el <strong>stack</strong>, ahora podemos usar la instrucción <code class="language-plaintext highlighter-rouge">call</code> y usar nuestra bien extraña <a href="https://wiki.osdev.org/Calling_Conventions">calling convention</a>.
Para pasar un parámetro a nuestra función, <code class="language-plaintext highlighter-rouge">pusheamos</code> el puntero del parámetro, hacemos un <code class="language-plaintext highlighter-rouge">call</code> inmediatamente después
y al volver, sumamos un <code class="language-plaintext highlighter-rouge">word</code> (si el <code class="language-plaintext highlighter-rouge">push</code> fue un <code class="language-plaintext highlighter-rouge">word</code>) al registro <strong>stack pointer</strong> <code class="language-plaintext highlighter-rouge">sp</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ; start "stage1"
; TODO: make this a function,
; https://github.com/cfenollosa/os-tutorial/blob/master/07-bootsector-disk/boot_sect_main.asm
mov bx, 0x9000 ; es:bx = 0:0x9000 ; where in memory are we
; going to put the stage1 code
mov dh, 4 ; read 4 sectors
call load_disk
</code></pre></div></div>
<p>Acá simplemente configuramos los parámetros de nuestra función <code class="language-plaintext highlighter-rouge">load_disk</code> diciéndole que
los 4 sectores que vaya a leer, los copie a <code class="language-plaintext highlighter-rouge">0:0x9000</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ; uncomment the following lines from
; ------------ here ----------------
;push word 0x9000
; call print
;add sp, 2
;cli
;hlt
; ------------till here------------
; for seeing that the sector data
; is displayed
jmp 0x9000 ; execute stage1
hlt
</code></pre></div></div>
<p>Y una vez copiados, saltamos al vacío a una dirección de memoria donde no pusimos todavía nada.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
loading_msg: db "Loading tOSh ver -3.1416a | tw/github: @0x705h ...", 0x07, 0
load_error_msg: db "Load error", 0
sectors_error_msg: db "Sectors error", 0
boot_drive_number: db 0
times 510 - ($-$$) db 'T' ; pad 510 bytes with 't', last two bytes
; in the 512 sector will be the bootloader mark
dw 0xaa55 ; bootloader mark. These bytes make this sector bootable
; here ends the first 512 bytes sector
</code></pre></div></div>
<p>Al final de nuestro <code class="language-plaintext highlighter-rouge">bootsector.asm</code>, rellenamos con <code class="language-plaintext highlighter-rouge">T</code> para poder ver como se va comportando nuestro
binario y si nos queda margen. El caracter <code class="language-plaintext highlighter-rouge">T</code> lo uso como padding a 512 bytes y ver cuanto espacio me queda.
Menos <code class="language-plaintext highlighter-rouge">T</code>s, menos bytes para programar el bootloader.</p>
<p>Finalmente, la <code class="language-plaintext highlighter-rouge">dw</code> <strong>word 0xaa55</strong> es una marca o <strong>número mágico</strong> que la BIOS espera encontrarse en ese offset exacto (511b y 512b respectivamente)
para darle el hint que ese sector es, efectivamente, <strong>booteable</strong>.</p>
<h1 id="mapa-de-memoria--layout-sectores">Mapa de Memoria & Layout Sectores</h1>
<p>En cada parte de esta serie de artículos, hay una parada obligada que va a ser mantener actualizado
nuestro mapa de memoria.
Necesitamos siempre tener en cuenta donde vamos a poner que en memoria y ser prolijo con esto nos va a evitar
<strong>muchisimos</strong> dolores de cabeza.</p>
<h3 id="floppy">Floppy</h3>
<table>
<thead>
<tr>
<th>Sector (CHS)</th>
<th>área Lógica</th>
<th>Contenido/Código</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td>C:0 H:0 S:1</td>
<td>boot sector</td>
<td>bootloader.asm</td>
<td>Lee contenido del floppy, lo copia a memoria y salta a <code class="language-plaintext highlighter-rouge">stage1.asm</code></td>
</tr>
</tbody>
</table>
<h3 id="memoria-modo-real">Memoria (Modo Real)</h3>
<table>
<thead>
<tr>
<th>Dirección Física</th>
<th>Notación Seg:Offset</th>
<th>Tamaño</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x00000 - 0x003FF</td>
<td>0000:0000</td>
<td>1 KB</td>
<td>IVT (Interrupt Vector Table de la BIOS)</td>
</tr>
<tr>
<td>0x00400 - 0x004FF</td>
<td>0000:0400</td>
<td>256 B</td>
<td>BDA (BIOS Data Area)</td>
</tr>
<tr>
<td>0x00500 - 0x07BFF</td>
<td>0000:0500</td>
<td>~29.7 KB</td>
<td>Zona libre / Nuestro Stack (apuntado por SP=0x7C00 decreciendo)</td>
</tr>
<tr>
<td>0x07C00 - 0x07DF_</td>
<td>0000:7C00</td>
<td>512 B</td>
<td>Boot Sector (bootsector.asm cargado por la BIOS)</td>
</tr>
<tr>
<td>0x07E00 - 0x8FFFF</td>
<td>0000:7E00</td>
<td>~544 KB</td>
<td>Memoria Convencional Libre</td>
</tr>
<tr>
<td>0x09000 - 0x097FF</td>
<td>0000:9000</td>
<td>2 KB</td>
<td>Stage 1 (Donde la INT 13h copia los 4 sectores del disco)</td>
</tr>
<tr>
<td>0xA0000 - 0xFFFFF</td>
<td>A000:0000</td>
<td>384 KB</td>
<td>"Memoria de Video, VRAM y ROM de la BIOS"</td>
</tr>
</tbody>
</table>
<h1 id="referencias">Referencias</h1>
<ul>
<li><a href="https://wiki.osdev.org/Boot_Sequence">Boot_Sequence</a></li>
<li><a href="https://en.wikipedia.org/wiki/Terry_A._Davis">Terry A. Davis</a></li>
<li><a href="https://en.wikipedia.org/wiki/TempleOS">Temple OS</a></li>
<li><a href="https://wiki.osdev.org/Real_Mode">Real Mode</a></li>
<li><a href="https://wiki.osdev.org/Protected_Mode">Modo Protegido</a></li>
<li><a href="http://www.ctyme.com/rbrown.htm">Ralf Brown’s Interrupt List</a></li>
<li><a href="https://wiki.osdev.org/Calling_Conventions">calling convention</a></li>
<li><a href="https://wiki.osdev.org/MBR_%28x86%29">MBR</a></li>
</ul>