Cuando compilamos código en C el compilador decide en que posición del binario debe insertar cada parte de código y datos, hasta ahora eso no ha sido un problema ya que el kernel solo tenÃa una función, pero conforme vaya creciendo puede que las primeras instrucciones no correspondan con la función main().
Antes de comenzar es recomendable que leas estos artÃculos anteriores:
- Boot Sector
- Interrupciones
- Memoria
- Pila
- IF-ELSE
- Funciones
- Segmentación de memoria
- Lectura de datos desde disco
- Entrando a modo protegido 32bits
- Compilación, linkado, gestión de la pila y variables en C
- Punteros
- Kernel
Veamos como quedarÃa el siguiente código:
void some_function () {
}
void main () {
char* video_memory = (char*) 0xb8000;
*video_memory = 'X';
some_function ();
}
Compilamos:
Mostramos el código ASM:
kernel2.o: file format elf32-i386-freebsd
Disassembly of section .text:
00000000 <some_function>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 5d pop ebp
4: c3 ret
5: 90 nop
6: 90 nop
7: 90 nop
8: 90 nop
9: 90 nop
a: 90 nop
b: 90 nop
c: 90 nop
d: 90 nop
e: 90 nop
f: 90 nop
00000010 <main>:
10: 55 push ebp
11: 89 e5 mov ebp,esp
13: 50 push eax
14: b8 00 80 0b 00 mov eax,0xb8000
19: 89 45 fc mov DWORD PTR [ebp-0x4],eax
1c: 8b 45 fc mov eax,DWORD PTR [ebp-0x4]
1f: c6 00 58 mov BYTE PTR [eax],0x58
22: e8 d9 ff ff ff call 0 <some_function>
27: 83 c4 04 add esp,0x4
2a: 5d pop ebp
2b: c3 ret
Generamos la imagen de disco con el kernel nuevo:
cat boot_sect.bin kernel2.bin > os-image2
Mostramos los opcodes de la imagen de disco:
00000000: 8816 5c7d bd00 9089 ecbb 5d7d e80b 00e8 ..\}......]}....
00000010: 1a00 e820 01e8 ee00 ebfe 608a 073c 0074 ... ......`..<.t
00000020: 09b4 0ecd 1083 c301 ebf1 61c3 60b4 0eb0 ..........a.`...
00000030: 0acd 10b0 0dcd 1061 c360 b900 0083 f904 .......a.`......
00000040: 741c 89d0 83e0 0f04 303c 397e 0204 07bb t.......0<9~....
00000050: 6b7c 29cb 8807 c1ca 0483 c101 ebdf bb66 k|)............f
00000060: 7ce8 b6ff 61c3 3078 3030 3030 0060 52b4 |...a.0x0000.`R.
00000070: 0288 f0b6 00b5 00b1 02cd 1372 075a 38f0 ...........r.Z8.
00000080: 7512 61c3 bb9c 7ce8 90ff e89f ff88 e6e8 u.a...|.........
00000090: a7ff eb06 bbac 7ce8 80ff ebfe 4469 736b ......|.....Disk
000000a0: 2072 6561 6420 6572 726f 7200 496e 636f read error.Inco
000000b0: 7272 6563 7420 6e75 6d62 6572 206f 6620 rrect number of
000000c0: 7365 6374 6f72 7320 7265 6164 0000 0000 sectors read....
000000d0: 0000 0000 00ff ff00 0000 9acf 00ff ff00 ................
000000e0: 0000 92cf 0017 00cd 7c00 0060 ba00 800b ........|..`....
000000f0: 008a 03b4 403c 0074 0b66 8902 83c3 0183 ....@<.t.f......
00000100: c202 ebed 61c3 fa0f 0116 e57c 0f20 c066 ....a......|. .f
00000110: 83c8 010f 22c0 ea1b 7d08 0066 b810 008e ...."...}..f....
00000120: d88e d08e c08e e08e e8bd 0000 0900 89ec ................
00000130: e816 0000 00bb 997d e8df fee8 eefe bb00 .......}........
00000140: 10b6 018a 165c 7de8 23ff c3bb 797d 0000 .....\}.#...y}..
00000150: e896 ffff ffe8 a692 ffff ebfe 0053 7461 .............Sta
00000160: 7274 6564 2069 6e20 3136 2d62 6974 2052 rted in 16-bit R
00000170: 6561 6c20 4d6f 6465 004c 616e 6465 6420 eal Mode.Landed
00000180: 696e 2033 322d 6269 7420 5072 6f74 6563 in 32-bit Protec
00000190: 7465 6420 4d6f 6465 004c 6f61 6469 6e67 ted Mode.Loading
000001a0: 206b 6572 6e65 6c20 696e 746f 206d 656d kernel into mem
000001b0: 6f72 7900 0000 0000 0000 0000 0000 0000 ory.............
000001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
00000200: 5589 e55d c390 9090 9090 9090 9090 9090 U..]............
00000210: 5589 e550 b800 800b 0089 45fc 8b45 fcc6 U..P......E..E..
00000220: 0058 e8d9 ffff ff83 c404 5dc3 .X........].
Podemos ver que el segundo sector(nuestro kernel) empiza con los opcodes de la función some_function() no del main(), de este modo cuando se salte a la posición de memoria 0x1000 se ejecutará some_function(), ejecutará la instrucción ret, volverá al bootloader y finalizará.
Para solventar este problema la mayorÃa de SOs utilizan un pequeño truco, utilizan una rutina en ensamblador que buscará la etiqueta main y la llamará, la etiqueta main es externa a la rutina por lo tanto hay que definirla como extern.
; Ensures that we jump straight into the kernel ’s entry function.
[ bits 32] ; We ’re in protected mode by now , so use 32 - bit instructions.
[ extern main ] ; Declare that we will be referencing the external symbol main
call main ; invoke main () in our C kernel
jmp $ ; Hang forever when we return from the kernel
Compilamos la rutina, cabe destacar que vamos a generar un binario elf, esto es necesario si queremos utilizar la sentencia extern.
Linkamos la rutina de carga y el kernel, el linkador sustituirá la etiqueta main por la dirección dentro del binario donde esté el código correspondiente a la función.
Este truco funciona porque el linkador respeta el orden en el que se indican los objetos a linkar, en este caso kernel_entry.o kernel2.o, generamos la imagen de disco:
La cargamos en Qemu: