>> Lista de Núcleos de Entrenamiento (solamente Nivel 1 por ahora) <<
El kernel al que me refiero ha llegado a lo que yo llamo el Nivel 1 de desarrollo.
Eso significa que es el nivel mínimo de usabilidad de un núcleo, que pueda serle útil a un usuario.
El nivel mínimo funcionalmente utilizable (Nivel 1) es poder leer programas del directorio raíz de un floppy mediante sus nombres cortos de archivo, enlazado dinámico básico, funciones de sistema exportadas para los programas, entrada de usuario con un teclado PS/2 y una consola de comandos muy básica.
-------------------------
En lo personal, escribir en ensamblador me es bastante fácil, y sería perfecto si el ensamblador x86 se usara por el resto de plataformas.
Pero ya que ese no es el caso, existen lenguajes como C, que tienen convenciones definidas para codificar sus tipos de datos, y llamadas a rutinas, pasado de parámetros, etc.
Este es una pequeña coyuntura clave: Escribí un programa en C que usa las funciones exportardas del kernel. Al comparar la forma en que se escribe un programa en C, su código generado y la falta de convenciones que usan las funciones de mi kernel actual, me llego a dar cuenta que puedo terminar de robustecer mi kernel si insisto en escribir programas en C y en ensamblador, e incluso reescribir el kernel en C y en ensamblador para comparar el código generado, y empezar a adoptar las convenciones, para obtener una portabilidad e interoperabilidad máximas.
Ya que los estándares de C y las convenciones necesarias son muy voluminosas y técnicas, esto no puede tomarse como una adivinanza, así que voy a tener que reunir esos documentos de especificaciones y traducirlos para estudiarlos a profundidad.
También voy a necesitar aprender a programar en C y en ensamblador para más de una plataforma, para poder ser capaz de crear código portable en todos los niveles. Debería por ejemplo aprender una plataforma ARM además de x86-16, x86-32 y x86-64.
-------------------------
Al usar las funciones del kernel como las tengo actualmente, me doy cuenta de que necesito especificar registros del CPU en el código de C a compilarse por GCC. Esto puede hacerse portable si uso mi lenguaje y compilador Real C, pero en GCC eso es un problema de portabilidad de plataformas enorme.
Este es el primer punto en el que voy a robustecer el kernel: Voy a aprender cómo codificar cualquier tipo de parámetros de funciones y variables de todo tipo.
El código de un programa escrito en C requiere más trabajo y más archivos, algo como esto:
"include/external_exported_functions.inc"
Código:
;Nota: Este archivo es para ser usado por programas externos,
; para que sepan dónde encontrar las funciones que desean
; usar, desde la tabla de funciones exportadas del kernel.
;Estos son los ÍNDICES de cada puntero en la tabla de exportes.
;Las convertimos a posiciones de 4 bytes para programas de 32 bits,
;y a posiciones de 8 bytes para programas de 64 bits.
;;
clrscr@KernelCore equ 0
console_kprint_visible@KernelCore equ 1
console_doCRLFeffect@KernelCore equ 2
cmdLine__findFirstBlankSpace@KernelCore equ 3
cmdLine__findFirstNonBlankSpace@KernelCore equ 4
adjustCommandLineFATname@KernelCore equ 5
getCommandLineArgsCount@KernelCore equ 6
copyCommandLineArg@KernelCore equ 7
cmdLine__charTypeAt@KernelCore equ 8
cmdLine__skipNonBlankSpaces@KernelCore equ 9
cmdLine__skipBlankSpaces@KernelCore equ 10
"exports.asm"
Código:
;Aquí anteponemos un guión bajo a todas las
;funciones de ensamblador a ser llamadas desde C;
;esto es llamado "mangling" o decoración, y es
;una convención de C/el enlazador, para enlazar
;funciones externas al código de C, o entre módulos,
;especialmente entre lenguajes que no comparten las
;convenciones de C, como el caso del ensamblador.
;;
%include "include/external_exported_functions.inc"
global myImportsFunctionsTable
global _clrscr
global _console_kprint_visible
global _console_doCRLFeffect
global _adjustCommmandLineFATname
global _getCommandLineArgsCount
global _copyCommandLineArg
global _cmdstrbuff
myImportsFunctionsTable:
ImportsCount dd 6
_clrscr dd clrscr@KernelCore
_console_kprint_visible dd console_kprint_visible@KernelCore
_console_doCRLFeffect dd console_doCRLFeffect@KernelCore
_adjustCommandLineFATname dd adjustCommandLineFATname@KernelCore
_getCommandLineArgsCount dd getCommandLineArgsCount@KernelCore
_copyCommandLineArg dd copyCommandLineArg@KernelCore
_cmdstrbuff times 1024 db 0
"header.asm"
Código:
;Aquí hemos optado por establecer a APPBASE
;simplemente a la ubicación actual en el código,
;representada por el signo de dólar, $.
;
;Ahora no es nuestro código en ensamblador el que
;debe encargarse de establecer la dirección base
;del ejecutable, sino que el enlazador LD, por el
;hecho de que estamos usando varios módulos por
;separado, y en esta complejidad solamente el enlazador
;puede llevar a cabo esto eficientemente.
;;
;APPBASE equ 1048576*2
APPBASE equ $
extern myImportsFunctionsTable
;Nuestro kernel debe leer esta dirección, saltar
;a esta y luego saltarse la cabecera del ejecutable:
;;
IntendedBaseAddress dq APPBASE
NumberOfFieldsBelow dq 7
CommandLinePtr dq 0
CommandLineLength dq 0
CommandLineParamsPtr dq 0
KernelFnExportsTable dq 0
KernelVrExportsTable dq 0
AppFnImportsTable dq myImportsFunctionsTable
AppVrImportsTable dq 0
"start.asm"
Código:
;Si hay código presente, debemos usar
;BITS 32:
;;
bits 32
;La etiqueta llamada "start"
;debe ser globalmente conocida, para que
;LD o más bien otros módulos de este
;programa pueda encontrarla adecuadamente.
;
;Este es el punto de entrada de nuestra
;aplicación.
;
;"start" es el símbolo que el script LD
;para esta aplicación busca para establecer
;el punto de entrada de la misma, y debe
;declararse como global.
;;
global start
;Estas son las funciones del kernel, con
;un guión bajo al inicio ("mangled") para
;que C pueda encontrarlas.
;
;Indicamos que son externas porque estos símbolos
;están definidos en "exports.asm":
;;
extern _clrscr
extern _console_kprint_visible
extern _console_doCRLFeffect
extern _adjustCommmandLineFATname
extern _getCommandLineArgsCount
extern _copyCommandLineArg
start:
;Limpiamos la pantalla como siempre:
;;
call dword[_clrscr]
;Declaramos el símbolo "_main" como
;externo, ya que se encuentra en "main.c",
;y llamamos a main, que está declarado como
;int main(void), al menos de acuerdo a nuestro
;kernel actual y también para esta aplicación
;específica.
;
;Debemos aprender a implementar
;int main(int argc, char **argv)
;por nosotros mismos:
;;
extern _main
call _main
;Aquí le devolvemos el control
;al kernel:
;;
ret
"main.c"
Código:
//Ya que nuestras funciones importadas
//están en una tabla de punteros de funciones,
//debemos declarar la función como un puntero
//a una función, para generar el código adecuado:
///
extern void (*console_doCRLFeffect)(void);
int main(void)
{
//Aquí colocaremos una lista de instrucciones
//para demostrar la sintaxis de AT&T de GAS
//para ensamblador inline, de todas las
//combinaciones de opcodes y expresiones que
//sea útil recordar:
///
__asm__ __volatile__ ("pushl %eax");
__asm__ __volatile__ ("popl %eax");
//Llamamos nuestra función proveniente
//del kernel. Ya que esta no toma parámetros,
//podemos usarla libremente sin temor a
//un mal manejo de los parámetros y la pila.
//Pero esto será un problema para funciones de
//la librería del kernel que usen parámetros, a
//menos que estén totalmente de conformidad a
//las convenciones de C (cdecl):
///
console_doCRLFeffect();
//Devolvemos 0 desde main, que en las x86
//lo coloca en AX/EAX/RAX:
///
return 0;
}
"link.ld"
Código:
OUTPUT_FORMAT("binary")
ENTRY(start)
phys = 0x0000000000200000;
SECTIONS
{
.text phys : AT(phys) {
code = .;
*(.text)
*(.rodata)
. = ALIGN(0);
}
.data : AT(phys + (data - code))
{
data = .;
*(.data)
. = ALIGN(0);
}
.bss : AT(phys + (bss - code))
{
bss = .;
*(.bss)
. = ALIGN(0);
}
end = .;
}
"build.bat"
Código:
nasm -f aout -o header.o header.asm
nasm -f aout -o start.o start.asm
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -c -o main.o main.c
nasm -f aout -o exports.o exports.asm
ld -T link.ld -o kern0 header.o start.o main.o exports.o
En "build.bat" (o un script Bash de Linux), el orden en el que se compilan los diferentes componentes es arbitrario; pero el orden en que se enlazan con LD es físicamente el mismo que el especificado en la línea de comandos, y debe estar en el orden mostrado aquí (cabecera del programa, inicializador en ensamblador, programa principal, otros archivos de código opcionales, y la tabla de exportes).
link.ld está configurado para generar un archivo binario crudo, y usar una alineación de 0 entre secciones.
Con un script más personalizado, podríamos indicar un ejecutable crudo pero encargarnos nosotros mismos de generar a mano el formato del ejecutable (PE, ELF, AOUT, MZ-EXE, etc.).
Ya que estamos usando el formato AOUT para crear el código objeto de cada pieza del programa mostrado aquí, también deberíamos aprender a manejar dicho formato a bajo nivel, para eventualmente crear herramientas propias y llevar a cabo tareas de depuración y desarrollo más potentes.