viernes, 13 de febrero de 2009

Códigos para HAL

En la anterior entrada nos quedamos a un paso de conseguir que HAL leyese nuestro guión inicial, el fichero llamado prueba, que reside en el directorio guiones.

Lo que evidentemente queremos, para ser más concretos, es que HAL ejecute la orden u órdenes que hay en ese fichero, en nuestro caso la orden pwd. ¿Cómo lograrlo?

Hasta ahora hemos hablado con HAL desde la línea de órdenes. Bastaba con poner ahí la orden que fuera para que HAL la ejecutase de inmediato. Pero HAL lo que realmente ve en un primer momento es una palabra como otra cualquiera. Y ya sabemos que podemos poner ahí cualquier palabra, sin que nadie se oponga. Por ejemplo:

hola

A lo cual, HAL nos responde con lo que ya lamentablemente hemos probado varias veces en nuestras propias carnes:

bash: hola: command not found

Extraigamos unas primeras conclusiones de este hecho evidentísimo.

La primera es que una orden tiene un nombre, justo el que escribimos en la línea de órdenes. La segunda es que no cualquier nombre, cualquier palabra, es el nombre de una orden, o, lo que es lo mismo, que una de las primerísimas cosas que comprueba HAL cuando nos comunicamos con él es si la palabra introducida en la línea de órdenes corresponde o no al nombre de una orden existente en el sistema.

Utilicemos la primera conclusión ---toda orden tiene un nombre--- para construir un interesante razonamiento. Si para HAL todo es un fichero ---algo de lo que hemos hablado en varias ocasiones--- y todo fichero se identifica en el sistema con un nombre, es obvio que al nombre de una orden le debe corresponder también un fichero del sistema.

Este dato es de una importancia capital y constituye una de las revelaciones fundamentales para todo usuario principiante, su rito de paso de usuario bobalicón a incipiente hacker. Todo lo que HAL hace una y otra vez es leer ficheros que están dentro de su propio sistema y ejecutar la instrucción o instrucciones que éstos contienen.

¿Donde están estos ficheros? ¿Qué clase de ficheros son las órdenes? Estos ficheros se encuentran habitualmente en directorios que contienen en su ruta el término bin (una abreviatura de binario) o sbin (binarios del sistema). Que tales ficheros se denominen binarios obedece a una razón que comprenderemos dentro de un momento. Cuatro de estos directorios típicos, lugares de residencia habitual de los ficheros binarios, son los siguientes (especificados con su ruta completa):

/bin
Órdenes esenciales

/sbin
Órdenes esenciales del sistema

/usr/bin
Órdenes no esenciales

/usr/sbin
Órdenes no esenciales del sistema

Podemos pedir a HAL que nos muestre, por ejemplo, qué ficheros contiene el directorio /bin:

ls /bin

Mi HAL devuelve un resultado de 99 órdenes esenciales, nada menos. Pidámosle una lista reducida, por ejemplo, la lista de aquellos ficheros cuyos nombres empiezan por una letra entre la 'c' y la 'g', ambas inclusives:

ls /bin | grep '^[c-g]'

En la lista resultante topamos con algún viejo conocido nuestro (destacado en rojo):

cat
chgrp
chmod
chown
cp
cpio
csh
date
dd
df
dir
dmesg
dnsdomainname
echo
ed
egrep
false
fgconsole
fgrep
fuser
grep
gunzip
gzexe
gzip


Sabemos que una orden es un fichero y sabemos dónde poder encontrar estos peculiares e importantísimos ficheros. Pero ¿qué clase de ficheros son?

La orden file (fichero, en inglés) sirve justamente para obtener esa información. Ciñamos nuestra pregunta a un caso concreto, digamos, al fichero /bin/echo:

file /bin/echo

Y HAL nos responde lo siguiente:

/bin/echo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, stripped

Nos interesa especialmente la primera indicación:

ELF 64-bit LSB executable

Se trata de una descripción muy técnica, de la cual la segunda y tercera parte son específicas de mi maquina ---el lector curioso puede seguir los enlaces a la Wikipedia para cada término de la descripción. Aquí nos interesa destacar exclusivamente un dato, a saber, que /bin/echo es un fichero binario. Lo que esto significa nos desvía del asunto principal de este artículo. Pero merece la pena detenerse un momento.

Si tratásemos de ver lo que hay en ese fichero con las órdenes que conocemos, por ejemplo, con more:

more /bin/grep

obtendríamos la siguiente expeditiva respuesta de HAL:

*** /bin/echo: No es un fichero de texto ***

Podríamos probar con alguna otra orden, por ejemplo con la orden strings (cadenas, en inglés), cuyo propósito es mostrar las cadenas, esto es, secuencias de caracteres, que contiene el fichero:

strings /bin/echo

He aquí, las diez primeras cadenas de bin/echo:

/lib64/ld-linux-x86-64.so.2
__gmon_start__
libc.so.6
setlocale
mbrtowc
optind
dcgettext
error
iswprint
realloc

Pero, aparte de que esto es prácticamente ininteligible para la mayoría de los mortales, ni siquiera es el contenido real del fichero, sino únicamente algunas pocas partes inconexas.

Una información mucho más precisa la proporciona una orden como readelf que, como su nombre indica, lee el fichero ELF dado como argumento:

readelf -x4 /bin/echo

La respuesta es más incomprensible aún, pero nos pone delante de la realidad, verdadera y temible a un tiempo. Veámosla sin miedo:

Hex dump of section '.gnu.hash':
0x00400378 03000000 23000000 01000000 06000000 ....#...........
0x00400388 02810100 00011802 23000000 24000000 ........#...$...
0x00400398 26000000 291d8c1c c0b3f712 39f28b1c &...).......9...
0x004003a8 33c4f712 3...

Esta secuencia de números sin sentido para nosotros es lo más parecido a lo que finalmente pondrá en marcha los engranajes de HAL al ejecutar la orden echo. Se trata de representaciones numéricas de cadenas de dígitos binarios (ceros y unos), que es lo que en último término digieren los intestinos de HAL.

Es inútil seguir por este camino. La realidad es extraordinaria y pavorosa a la vez. Necesitamos interfaces para protegernos de ella y, sobre todo, para poder tratarla de alguna manera.

El hombre, que con razón los griegos llamaron ser racional (de λόγος), que en griego significa también ser lingüístico, debe pasar la realidad por el tamiz de la lengua y eso es lo que hicieron desde el principio los creadores de HAL. Las entrañas de HAL sólo tratarán números, pero el hombre se desenvuelve en el reino de las palabras. La encrucijada se resolvió creando traductores de textos a números, o más exactamente, cadenas de traductores que iban aproximando cada vez más la lengua del hombre a la secuencia numérica que hace funcionar la maquinaria.

Podemos poner entre paréntesis toda la cadena de traducción y quedarnos en los dos extremos: lo escrito por el hombre y la traducción a los números. Los números ya los hemos visto. ¿Qué hay en el otro extremo? Sencillamente un fichero de texto normal y corriente que contiene las instrucciones que, una vez procesadas por la cadena de traductores, se convierten en números, un fragmento de los cuales entrevimos antes.

¿Se puede ver ese fichero, el código que el humano escribió para HAL? Se puede, y ésta es una de las ventajas mayores de usar HAL como nuestra máquina. Podemos descargar el código como cualquier otro paquete. Pongamos ante los ojos de los incrédulos un fragmento de ese fichero de texto, un trozo de la orden echo tal y como salió de la pluma de su creador:

int
main (int argc, char **argv)
{
bool display_return = true;
bool allow_options =
(! getenv ("POSIXLY_CORRECT")
|| (! DEFAULT_ECHO_TO_XPG && 1 < argc && STREQ (argv[1], "-n")));

/* System V machines already have a /bin/sh with a v9 behavior.
Use the identical behavior for these machines so that the
existing system shell scripts won't barf. */
bool do_v9 = DEFAULT_ECHO_TO_XPG;

initialize_main (&argc, &argv);
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);

atexit (close_stdout);

if (allow_options)
parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, VERSION,
usage, AUTHORS, (char const *) NULL);

Lo que para el lector resulta incomprensible en este texto es de un orden radicalmente diferente a la ininteligibilidad del fragmento numérico en que finalmente se convierte. El texto está escrito en una lengua artificial determinada, que se llama lenguaje de programación C, una de las lenguas más usadas para escribir secuencias de instrucciones u órdenes para HAL. Cualquier humano puede aprender esa lengua, del mismo modo que estamos aprendiendo lo que hemos estado llamando, no sin cierta imprecisión, "la lengua de HAL" y que no es sino otra de las lenguas que HAL entiende.

Ahora bien, existe una diferencia notable en el proceso de traducción a números entre los textos escritos en C y los escritos en la así llamada por nosotros "lengua de HAL". Mientras que los primeros necesitan ser traducidos completamente antes de que HAL pueda ejecutar las instrucciones que contienen, los textos escritos en la lengua que estamos aprendiendo se traducen a medida que se van escribiendo, y por tanto son ideales para una comunicación interactiva, un diálogo en tiempo real, una conversación con HAL, como la que hemos estado realizando.

Entre las órdenes que habitan en los directorios donde residen los ficheros ejecutables hay, por supuesto, unas cuantas escritas directamente en "la lengua de HAL". Veamos, por ejemplo, el comienzo del fichero /bin/which [Se lo pedimos a HAL con un simple more /bin/which]:

#! /bin/sh
set -ef

if test -n "$KSH_VERSION"; then
puts() {
print -r -- "$*"
}
else
puts() {
printf '%s\n' "$*"
}
fi

ALLMATCHES=0

De nuevo, difícil del entender, pero más próximo a lo que ya conocemos. Quizá al final de estas conversaciones el lector sepa leer y escribir textos como éste.

De las anteriores consideraciones podemos concluir que nuestro propósito de editar un fichero para que HAL lea de él las órdenes que contiene no es nada extraordinario, sino justamente lo más común y natural, porque, como hemos demostrado largamente, esto es en definitiva lo que continuamente está haciendo HAL, y es indiferente, en este sentido, que el fichero que lea sea un fichero binario o un fichero escrito en la llamada "lengua de HAL". Al fin y al cabo, siempre se tratará de una ristra de instrucciones que HAL ejecutará con la infalibilidad del más fiel de nuestros amigos.


Resumen:

  • Todas las órdenes que HAL nos proporciona de antemano no son más que ficheros ejecutables de su sistema.

  • Los directorios /bin, /sbin, /usr/bin y /usr/sbin contienen la mayor parte de los ficheros ejecutables en sistemas GNU/Linux.

  • La orden file permite conocer qué tipo de fichero es aquél que se le da como argumento.

  • Un fichero binario es aquél que contiene datos codificados en secuencias de ceros y unos. El formato típico de fichero binario para HAL es ELF.

  • La orden strings sirve para extraer las cadenas (secuencias de caracteres) contenidas en un fichero binario.

  • La orden readelf muestra información de ficheros en formato ELF.

  • Un fichero binario es el resultado de traducir o convertir un fichero escrito en un lenguaje de programación a la secuencia numérica que ejecutará la máquina.

  • Los lenguajes de programación pueden dividirse en dos grandes grupos, dependiendo de cómo tenga lugar el proceso de traducción de los ficheros escritos en dicho lenguaje: aquéllos cuyos ficheros deben ser traducidos completamente antes de poder ejecutarse (lenguajes compilados) y aquéllos otros cuyas instrucciones se van traduciendo a medida que se van introduciendo (lenguajes interpretados).

  • Lo que hemos llamado "lengua de HAL" es un lenguaje de programación interpretado.

  • Un fichero escrito en un lenguaje de programación no es sino una serie de instrucciones que la máquina ejecuta.

No hay comentarios:

Publicar un comentario