/hispatec/ - Tecnologia hispana

Refugio del /t/ de hispa

Página principal Catálogo Archivar Bajar Actualizar
Opciones
Título
Mensaje

Máximo de caracteres: 8001

archivos

Tamaño máximo de archivo: 32.00 MB

Tamaño máximo de archivo en total: 50.00 MB

Numero máximo de archivos: 5

Supported file types: GIF, JPG, PNG, WebM, OGG, and more

Correo
Clave

(usado para eliminar archivos y mensajes)

Misc

Recuerda leer las reglas

The backup domains are located at 8chan.se and 8chan.cc. TOR access can be found here, or you can access the TOR portal from the clearnet at Redchannit 3.0.

Ghost Screen
No olvidó el anuncio global esta semana
Sábado en la noche


8chan.moe is a hobby project with no affiliation whatsoever to the administration of any other "8chan" site, past or present.

Otros tablones: /hisparefugio /ac /arte /av /hisrol /vt /arepa /esp /col /cc /mex /pe /hispol Contacto: drzmx@cock.li SI TU HILO ERA UNA CONSULTA SIMPLE, Y NO LO VES, SE HA FUSIONADO CON EL HILO GENERAL DE PREGUNTAS

(595.61 KB 811x599 2.png)

Ensamblador Anónimo 04/10/2023 (Mie) 12:12:48 656
Negros, hace tiempo que pienso en crear un hilo sobre desarrollo de sistemas operativos, así aprendemos juntos, basado en los libros "Sistemas operativos. Aspectos internos y principios de diseño" de Stallings y "Sistemas Operativos Modernos" de Tanenbaum. Pero creo que primero sería mejor que aprendamos Ensamblador. Por lo que estaba pensando que podríamos usar "Assembly Language for x86 Processors" de Kip Irvine o "Assembly Language Step-By-Step" de Jeff Duntemann. Aun que si es problema el ingles, podríamos aprender de "Lenguaje ensamblador para PC" de Paul A. Carter.
(606.20 KB 3120x4160 1617875124755.jpg)

>>657 Según los prefacios de cada libro, "Lenguaje ensamblador para PC" pretende enseñar el ensamblador de la versión 80386 y posterior, del cual resalta que tiene varias ventajas sobre la versión 8086, como el uso del modo protegido y el manejo de memoria virtual. Este libro usa NASM Netwide Assembler, pero solo cubre los temas "más importantes" según el autor. "Assembly Language Step-By-Step" quiere enseñar ensamblador para entender como funciona una computadora. También usa NASM, con el editor Kate y el debugger Gdb. En este caso cubre varios temas importantes en relación a la estructura de una computadora. "Assembly Language for x86 Processors" enseña a programar en ensamblador, y la estructura de los procesadores Intel y AMD. Usa MASM Microsoft Macro Assembler, incluye conceptos importantes para el desarrollo de sistemas operativos y compiladores. También algoritmos importantes en informática se puede profundizar más este tema en otro hilo sobre el estudio de algoritmos empezando con el libro "Introduction to Algorithms". Sigue la idea de que es más fácil retener conceptos e información si se acompaña la teoría con práctica. Incluye capítulos sobre el modo protegido, en relación a la programación de la BIOS para el teclado, gráficos y mouse. Dice que provee dos librerías para la entrada, salida, temporización, entre otras funciones, pero dice que se pueden crear librerías propias.
¿entonces estamos mas cerca de triangulOS? ayy fuera de meme, me parece interesante opecito
>>660 Pensaba empezar la lectura recién el 15 o 20 de Octubre para que mas negros vean el hilo. Aparte, ¿Cuál libro crees que es mejor para el hilo? Por mi parte creo que "Assembly Language for x86 Processors" por que tiene capítulos interesantes, como el de la BIOS.
>>657 Basándome en las diferentes opiniones y recomendaciones de foros relacionados al lenguaje ensamblador, creo que es mejor empezar con el libro "Programming from the Ground Up" de Jonathan Bartlett. Lo pueden descargar gratis de la pagina https://savannah.nongnu.org/projects/pgubook/, en la sección Área de Descarga. Aunque dejo el archivo para el que no quiera molestarse. Posiblemente, según vayamos avanzando, leeremos capítulos específicos de los libros que ya he mencioné.
Capitulo 1: Introducción. Donald Knuth, describe la programación no solo como instruir a una computadora, sino contarle a una persona como debería instruir a la computadora para que esta logre algo. Ósea que los programas van a ser leídos por muchas personas, no solo la computadora. El objetivo del programador es resolver un problema, enseñando tu solución a otro programador. La gran mayoría de libros introductorios suelen ser frustrantes -al menos para el autor- porque omiten cuestiones que son importantes solo por que son temas 'difíciles'. El libro te llevara a través de estos temas por que es la única forma de ser un programador magistral. El objetivo es empezar no sabiendo mucho de programación para terminar pensando, escribiendo, y aprendiendo como un programador. No vas a saber todo, pero tendrás un respaldo para entender como funciona todo. No se puede enseñar todo en un libro, por que la informática es un campo enorme, especialmente cuando combinas la teoría con la practica de la programación. Aunque se va a intentar Inciarte en los fundamentos para que puedas ir a donde quieras -dentro de la informática-. Hay un problema relacionado al huevo y la gallina en el aprendizaje de ensamblador. Hay mucho por aprender, al menos mucho para aprenderlo de un solo intento, por que cada pieza depende de todas las demás. Por esto debes ser paciente contigo mismo y la computadora mientras aprendes a programar ensamblador. Si no entiendes a la primera, reléelo. Si continuas sin entenderlo, tal vez es tiempo de descansar un poco y seguir después. Si aun así continuas sin entenderlo, tal vez necesitas mas exposición a la programación para que el concepto o idea tenga sentido. No te sientas desanimado, es un largo camino, pero muy beneficioso. Al final de cada capitulo hay tres conjuntos de ejercicios. El primer conjunto es repetitivo, verifica si puedes retener lo que aprendiste en el capitulo. El segundo conjunto contiene preguntas sobre aplicación, verifican si puedes aplicar lo que aprendiste para resolver problemas. El conjunto final es para abrir los horizontes. Algunas de estas preguntas no van a tener respuestas hasta mas adelante en el libro, pero van a servir para que pienses un poco sobre el tema. Otras preguntas tal vez necesiten investigación para descubrir la mejor solución. Muchas de las preguntas no van a tener respuesta correcta o incorrecta, pero no significa que no sean importantes. Aprender temas que estén relacionados con la programación, aprender como investigar respuestas, y aprender como mirar para adelante son la mayor parte del trabajo del programador.
>>677 No es necesario leer este texto, solo es un resumen del pensamiento y experiencia del autor con relación a como suelen ensañar programación.
(1.02 MB 498x331 1.gif)

Tus herramientas Este libro enseña lenguaje ensamblador para procesadores x86 y el sistema operativo GNU/Linux, por lo que todos los ejemplos usaran el conjunto de herramientas estándar GCC. Si no estas familiarizado con GNU/Linux y este conjunto de herramientas, serán descritas brevemente a continuación. Si eres nuevo en Linux, deberías ver la guía disponible en Linux es el nombre del kernel, el cual es el núcleo de un sistema operativo, mantiene el rastro de todo. El kernel es, como lo describe el autor, tanto una barrera como una puerta. Como puerta permite a los programas acceder al hardware de una forma uniforme u homogénea. Sin el kernel, deberías escribir los programas para que trate directamente con cada modelo de dispositivo diseñado. El kernel maneja todos las interacciones especificas del dispositivo para que no tengas hacerlo todo tu mismo. También maneja el acceso a los archivo e interacciones entre procesos. Pro ejemplo, cuando se presiona una tecla, esta señal pasa a través de varios programas antes de que se vea en el editor. Primero el kernel maneja el hardware, por lo cual es el primer en recibir la señal. El teclado lo manda en código al kernel, este lo convierte en letras, números o símbolos. Si se esta usando un sistema de ventanas, como Windows, este sistema lee la tecla presionada desde el kernel y la entrega a cualquier programa que tenga el foco del display del usuario. Ejemplo 1-1. Manera en que se procesan las señales del teclado Teclado -> kernel -> sistema de pantalla -> programa de aplicación El kernel también controla el flujo de información entre programas. Este funciona como una puerta para el mundo que tiene al rededor. Cada vez que se mueven datos entre procesos, el kernel controla este sistema de mensaje. En el ejemplo anterior, el kernel esta involucrado entre la comunicación de la señal de la tecla presionada y la aplicación del programa. Como una barrera, el kernel previene que los programas 'accidentalmente' sobre-escriban la información y archivos de otros y el acceso a los dispositivos no autorizados. Limita la cantidad de daño que los programas, mal escritos arruinen otros programas. En nuestro caso el kernel es Linux, el kernel por si solo no hace nada, no puedes bootear -iniciar- una computadora solamente con el kernel, por que este necesita de aplicaciones de usuarios para funcionar como un sistema operativo completo. El kernel solamente es una capa de abstracción del hardware, o dicho de otra forma, un entorno para simplificar el acceso al hardware. Para la mayor parte, el libro va a usar el lenguaje ensamblador de bajo nivel. Hay esencialmente tres tipos de lenguajes: •Lenguaje maquina: Los que la computadora realmente lee y trabaja. Secuencia de números binarios. •Lenguaje ensamblador: Lo mismo que el lenguaje maquina, excepto que los comandos de números fueron reemplazados por secuencias de letras. •Lenguajes de alto nivel: Estos lenguajes hacen mas sencilla la programación. Ensamblador, en cambio, requiere que se trabaje con la maquina misma. Permite describir el programa en un lenguaje mas natural. Un solo comando de un lenguaje de alto nivel equivale a varios comandos de ensamblador. Por este libro se va a enseñar el lenguaje ensamblador, aunque también va a cubrir un poco del los lenguajes de alto nivel. Con suerte, al aprender ensamblador, comprenderás como se programa una computadora y como estas trabajan.
(40.08 KB 1024x731 arquitectura von Neumann.png)

Capitulo 2: La arquitectura de una computadora. Antes de adentrarse en la programación, primero se necesita entender como una computadora interpreta un programa. Las computadoras modernas están basadas en la arquitectura conocida como Von Neumann, esta divide a la computadora en dos partes principales, la CPU y la memoria. Esta arquitectura se usa en todas las computadoras, desde computadoras personales, hasta supercomputadoras.
Estructura de la memoria de una computadora. Para entender como la computadora ve a la memoria debes imaginar diferentes cajas, cada una de estas cajas está numerada en una secuencia numerada con un almacenamiento de tamaño fijo. Por ejemplo, como explica en el libro, si una computadora tiene solo 256 megabytes de memoria, quiere decir que tiene 256 millones de ubicaciones con un tamaño fijo de un byte cada uno. Una computadora se organiza de esta manera por el hecho de que es de simple implementación. Sería mala idea, para implementar, que cada bloque de memoria sea de un tamaño diferente. Todo lo que tenga que ver con la computadora y su estado se almacena en la memoria. En la memoria no solo se almacenan los datos, sino que también se guardan los programas que controlan la computadora, por lo que no hay diferencia entre los datos de un programa y el programa, sino el uso que les da la computadora.
El CPU. El CPU lee una instrucción de la memoria y luego la ejecuta. Esto se conoce como ciclo de ejecución de instrucciones. La CPU contiene varios elementos para lograr este funcionamiento, entre los que están: • Contador de programa • Decodificador de instrucciones • Bus de datos • Registros de uso general • Unidad aritmético lógica. El contador de programa le indica a la computadora cual es la siguiente instrucción a leer. Este registro contiene la dirección de memoria de esta instrucción. El CPU empieza leyendo el contador de programa, y obteniendo el numero que esta almacenado en la ubicación de memoria especificado. Se pasa este numero al decodificador de instrucción, para entender que operación tiene que ejecutar el CPU y cuales son los datos de esta operación. Por lo general la instrucción de ensamblador y código maquina consiste del nemotécnico o numero y una lista de ubicaciones de la memoria para llevar a cabo esta operación. Ejemplo de una instrucción: mov ebx, 0x9c0004 No es importante entender lo que hace, solo es un ejemplo de como es una instrucción de ensamblador. Ahora la computadora usa el bus de memoria para traer la información que esta almacenada en la ubicación, pasado tanto por el PC contador de programa como una dirección de memoria pasada a una instrucción. Este bus son literalmente unos cables que, para simplificar, van desde la CPU hasta la memoria. Yendo mas en profundidad, una CPU tiene varias memoria de muy alta velocidad, pero con poca cantidad de almacenamiento 4 bytes de almacenamiento si hablamos de registros de 32 bits. Estas memorias se dividen según el propósito: están los registros de propósito general, y de propósito especial. En los registros de propósito general es donde suele estar toda la información que se procesa. En cambio los registros de propósito especial tienen usos muy particulares, como el caso del registro ESP, el cual se usa como puntero a la pila de llamadas. Una vez que el CPU ya tiene todos los datos que necesita, pasa estos y la instrucción decodificada al ALU Unidad aritmético lógica, la cual va a ejecutar la operación. Cuando los datos ya fueron procesados, el resultado es enviado a una ubicación de memoria o almacenado en un registro. Obviamente es muy simplificada esta explicación, y creo que sería interesante desarrollar el funcionamiento del CPU en un hilo aparte.
>OP es loliconero gracias por matar mi interés en participar, animal lolicon=mierda
>>728 Negro, si te molesta dejo de subir imágenes, lo hacía para que no quedara muy denso.
Algunos términos. La memoria de una computadora es una secuencia numerada de ubicaciones con un tamaño fijo. Esta numeración es la dirección. El tamaño de esta ubicación suele ser de un byte, que es un numero entre 0 y 255. Cada byte tiene una interpretación, interpretación basada en la tabla ASCII. Esta tabla es la base de la traducción y presentación de caracteres en la pantalla. El libro bien explica que, por ejemplo, la letra mayúscula A es representada por el valor 65, mientras que la letra minúscula a es representada por el valor 97. Por eso en C, si tienes una variable que representa a la letra mayúscula, y a esta variable le sumas 32, obtienes el carácter 'a'. char mayuscula = 'A'; char minuscula = mayuscula + 32; Pero un byte no necesariamente tiene que representar algo en la tabla ASCII, sino que puede representar cualquier cosa que el programador necesite. Si se necesita representar un numero mayor a 255 lo único que sería necesario es combinar varios bytes. por ejemplo 4 bytes representarían un numero entre 0 y 4.294.967.295, un numero entre −2.147.483.648 y 2.147.483.647 si tienes que representar números enteros. Como mencioné antes y menciona el libro en este punto, los registros son el lugar de trabajo y almacenamiento de resultado de las operaciones. Estos registros tiene un almacenamiento de cuatro bytes -por lo general-. La cantidad de bits que una computadora procesa como unidad básica -puede ser un byte, dos bytes, etc.- es denominado tamaño de palabra. Las direcciones de memoria entran en un registro aunque en desarrollo de sistemas operativos existen varios tipo de direccionamiento de memoria, por lo que un procesador x86 puede acceder hasta 4294967296 direcciones de memoria. ///UN CONCEPTO IMPORTANTE A TENER EN CUENTA ES QUE EL VALOR QUE TIENE UNA DIRECCIÓN DE MEMORIA NO REPRESENTA NADA HASTA QUE NO SE VAYA A HACER ALGO CON ESE VALOR.\\\ Por ejemplo, si se accede a un valor, y se lo quiere mostrar por pantalla, entonces va a ser mostrado según la tabla ASCII o cualquier otro sistema de codificación. Ahora, podría ser tomado como una instrucción o un dato también. Nuevamente, la representación que se le de a un valor depende según el uso que se le vaya a dar. También esta la posibilidad de que un valor apunte a una dirección de memoria, en ese caso es un puntero muy conocido en C. La única forma que tiene el procesador de saber si un valor en memoria es una instrucción es según si este esta apuntado por un registro de propósito especial llamado Puntero de instrucción Instruction Pointer o Contador de programa o PC o CP.
Interpretando la memoria. La computadora no tiene la menor idea de lo que tu programa va a hacer, solo lo va a ejecutar siguiendo las instrucciones que tu especificaste. si le indicas a la computadora que ejecuta la instrucción en una dirección de memoria, esta lo va a hacer aunque esa ubicación no tenga una instrucción, sino un dato. Y hago énfasis en que la computadora solo va a ser lo que tu le hayas especificado en el programa. Por esta cuestión, como programador necesitas saber exactamente lo que vas a hacer, donde van a estar almacenados los datos en la memoria, etc. Ahora, el libro hace hincapié en la forma en la que se puede almacenar los datos en la memoria aunque hay mas métodos, todo este ejemplo basado en un comercio. Primero muestra una forma poco practica -por que limita el tamaño de los datos-. Ejemplo de memoria secuencial Comienzo del registro: Nombre del cliente (50 bytes) -> comienza dirección en 1016, termina en 1066 (1016 + 50) Dirección del cliente (50 bytes) -> comienza dirección en 1067, termina en 1117 (1067 + 50) Edad del cliente (4 bytes o una palabra) -> comienza en dirección 1118, termina en 1122 (1118 + 4) ID del cliente (4 bytes) -> comienza en dirección 1123, termina en 1127 (1123 + 4) Ahora, explica otra forma. Es mucho mas simple especificar la dirección de memoria en la que se encuentra el dato. Ejemplo punteros Comienzo del registro: Nombre del cliente (4 bytes) -> valor que apunta a la dirección de memoria. Dirección del cliente (4 bytes) -> valor que apunta a la dirección de memoria. Edad del cliente (4 bytes) -> valor que apunta a la dirección de memoria. ID del cliente (4 bytes) -> valor que apunta a la dirección de memoria. De esta forma, los datos se almacenan en otra parte de la memoria, evitando limitar el tamaño del dato.
Métodos de acceso a la memoria. Los procesadores tienen varias maneras de acceder a los datos, todas estas conocidas como métodos de direccionamiento. La forma mas sencilla es el método inmediato, ósea que los datos están integrados a la instrucción. El libro da un ejemplo muy bueno, inicializar un registro para que este tenga el valor 0. No se da una dirección que contenga el valor 0, sino que se especifica un cero. En el modo de direccionamiento de registro, la instrucción tiene un registro al que acceder mas que una ubicación de memoria. En el modo de direccionamiento directo, la instrucción contiene la dirección de memoria. Por ejemplo, decirle al CPU que cargue un valor x en una dirección de memoria especifica. En el método de direccionamiento indexado, la instrucción contiene una dirección de memoria de acceso y un registro índice como offset un desplazamiento de esa dirección. Ejemplo del libro: dirección de memoria: 2002 registro_indice: 4 direccion_real: 2002 + 4 = 2006 De esta forma, si quieres obtener los valores de un conjunto de ubicaciones contiguas una ubicación de memoria al lado de otra solo hay que ajustar la dirección de memoria y hacer un ciclo incremental con el registro índice. Ejemplo en pseudocódigo: direcciones objetivo: [1132, 1133, 1134, 1135, 1136] direccion_memoria = 1131 registro_indice = 1 dirección_a_acceder = 0 Mientras dirección_a_acceder <= 1136: dirección_a_acceder = direccion_memoria + registro_indice registro_indice = registro_indice + 1 Fin Mientras En los procesador x86 se puede usar la multiplicación del índice a tu favor, accediendo a la memoria byte por byte, o palabra a palabra. En el caso de acceder por medio de una multiplicación de palabra, el índice se va a multiplicar por el tamaño de esta 4 en el caso de x86. Ósea, si quisieras acceder byte a byte, debes ajustar el multiplicador a uno. Ejemplo en pseudocódigo: Dirección objetivo: 1154 direccion_memoria = 1131 registro_indice = 23 multiplicador = 1 direccion_a_acceder = direccion_memoria + (registro_indice * multiplicador) Pero si quieres acceder saltando palabras, deberías ajustar el multiplicador en 4 o al valor de tamaño de la palabra, y configurar el valor del registro índice en la cantidad de palabras a saltar -1. Ejemplo: Dirección objetivo: 1196 direccion_memoria = 1132 registro_indice = 15 multiplicador = 4 direccion_a_acceder = direccion_memoria + (registro_indice * multiplicador)
En el método indirecto de direccionamiento, la instrucción contiene un registro el cual contiene un puntero a la ubicación de memoria que contiene el dato a operar. Por ejemplo, si se quiere usar un registro que contiene el valor 4, cualquier valor que contenga la memoria en la posición cuatro va a ser usado en la operación. En cambio, en el método directo, se especifica el valor, no la dirección de memoria que contiene el valor. (((indirecto))) ->> quiero el valor que esta en la posición 100 de la memoria. (((directo))) --->> quiero el valor 100 valor, no dirección. Por ultimo, esta el direccionamiento por puntero base. En este caso se tienen dos valores: un valor de puntero base, y un valor offset un desplazamiento, el cual se suma al anterior para obtener la dirección real de memoria. Siguiendo el ejemplo de la estructura de cliente, suponiendo que queríamos obtener la dirección de la variable que contiene la edad del cliente, y teniendo en cuanta que la dirección de inicio de la estructura 1016 esta en un registro, se puede especificar este registro como puntero base. Luego se puede sumar a este registro puntero base el offset, que en este caso es 102. Dirección de memoria de la edad del cliente = Dirección base (1016) + Offset (102) = 1118 Es muy parecido al método de direccionamiento indexado, pero el offset es constante y el puntero se almacena en un registro. Ósea, a muy grandes rasgos: Inmediato: La dirección se especifica directamente en la (((instrucción))). De registro: La dirección se especifica en un (((registro))). Directo: La dirección se especifica como una (((dirección de memoria))). Indexado: La dirección se especifica como una (((dirección de memoria base más un desplazamiento))). Indirecto: La dirección se especifica como la (((dirección de memoria de un puntero))). Por puntero base: La dirección se especifica como la (((dirección de memoria base más un desplazamiento))). Estos son algunas de las formas de direccionamiento.
///Con esto entramos en los primero ejercicios o problemas planteados por el libro.\\\ Revisión. Conoce los conceptos • Describa el ciclo de búsqueda-ejecución. • ¿Qué es un registro? ¿Por qué sería más difícil calcular sin registros? • ¿Cómo se representan los números mayores que 255? • ¿Qué tamaño tienen los registros de las máquinas que vamos a utilizar? • ¿Cómo sabe un ordenador cómo interpretar un determinado byte o conjunto de bytes de memoria? • ¿Cuáles son los modos de direccionamiento y para qué se utilizan? • ¿Qué hace el puntero de instrucción? Usa los conceptos • ¿Qué datos utilizarías en un registro de empleados? ¿Cómo los distribuirías en memoria? • Si tuviera el puntero al principio del registro de empleados anterior y quisiera acceder a un dato concreto de su interior, ¿Qué modo de direccionamiento utilizaría? • En el modo de direccionamiento por puntero base, si tienes un registro con el valor 3122 y un offset de 20, ¿a qué dirección estarías intentando acceder? • En el modo de direccionamiento indexado, si la dirección base es 6512, el registro índice tiene un 5 y el multiplicador es 4, ¿a qué dirección estaría intentando acceder? • En el modo de direccionamiento indexado, si la dirección base es 123472, el registro índice tiene un 0 y el multiplicador es 4, ¿a qué dirección estarías intentando acceder? • En el modo de direccionamiento indexado, si la dirección base es 9123478, el registro índice tiene un 20 y el multiplicador es 1, ¿a qué dirección estarías intentando acceder? Yendo más lejos • ¿Cuál es el número mínimo de modos de direccionamiento necesarios para el cálculo? • ¿Por qué incluir modos de direccionamiento que no son estrictamente necesarios? • Investiga y describe cómo afecta el pipelining (u otro factor de complicación) al ciclo de búsqueda y ejecución. • Investiga y describe las ventajas y desventajas de las instrucciones de longitud fija y las de longitud variable.
(81.52 KB 624x636 Ejecucion de un programa.png)

Ciclo de búsqueda-ejecución Al comienzo del ciclo, la CPU lee una instrucción, la apuntada por el contador de programa. ///Algo que olvide mencionar es que el contador de programa es un contador incremental, al no ser que se indique una dirección diferente.\\\ Esta instrucción leída se almacena en el registro de instrucción, instrucción que fue traducida a binario. La CPU ejecuta la instrucción, la cual puede tener un efecto entre estos cuatro, o una combinación de estas: • Procesador-Memoria: copia datos de la CPU a la memoria o al revés. • Procesador-E/S: transfiere datos desde o hasta el entorno de la CPU por medio de los dispositivos de E/S. • Procesamiento de Datos: la CPU ha de realizar alguna operación aritmética o lógica con los datos. • Control: alteración de la ejecución. Por ejemplo, cambiar el valor del contador de programa. Además el libro de un buen ejemplo del ciclo de búsqueda-ejecución, en el ejemplo se suma el dato en la dirección 940 con el dato almacenado en la dirección 941 y lo almacena en esta ultima posición. 1. El contador de programa (PC) contiene el valor 300, la dirección de la primera instrucción. Esta instrucción (el valor hexadecimal 1940) se carga en el registro de instrucción (IR). Obsérvese que este proceso implicaría el uso del registro de dirección de memoria (MAR) y el registro de datos de memoria (MBR). Por simplicidad, se han ignorado estos registros intermedios. 2. Los primeros cuatro bits de IR (primer dígito hexadecimal) indican que el acumulador (AC) se va a cargar. Los restantes 12 bits (tres dígitos hexadecimales) especifican la dirección (940) que se va a cargar. 3. El registro PC se incrementa, y se capta la siguiente instrucción (5941) desde la dirección 301. 4. El contenido anterior de AC y el de la posición de memoria 941 se suman, y el resultado se almacena en AC. 5. El registro PC se incrementa, y se capta la siguiente instrucción (294) desde la posición 302. 6. El contenido de AC se almacena en la posición 941. Fuentes: "Organización y arquitectura de computadores" de William Stallings, 7 edición. ISBN 10: 84-8966-082-4 - ISBN 13: 978-84-8966-082-3 También hay un libro que parece muy interesante relacionado a este tema arquitectura de computadoras, "Computer Organization and Design: The Hardware/Software Interface" de David A. Patterson y John L. Hennessy. Si encuentro algo, en este libro, que complemente a lo anterior, lo postearé.
>>761 ¿Qué es un registro? ¿Por qué sería más difícil calcular sin registros? Un registro es una pequeña memoria al servicio del microprocesador. Esta memoria se ubica en lo mas alto de la jerarquía de memorias, por lo que es mas costosa por bit, tiene menos espacio y es mas rápida que las demás memorias en esta jerarquía. Los registros pueden dividirse según si el programador puede acceder y cambiarlos o no. • Registros visibles por el usuario: Permiten al programador minimizar el acceso a la memoria al permitir almacenar datos. • Registros de control y estado: Usados para controlar el funcionamiento del procesador y la ejecución de los programas Aunque esta división varia según el microprocesador. La segunda imagen de un ejemplo de como se puede organizar los registros de tres microprocesadores distintos. ------------------------------------------------------------------------------------------------------------------------------------------- Registros visibles por el usuario Estos tipos de registros pueden referenciarse por medio del lenguaje maquina. Dentro de estos registros hay varias categorías según el uso que se le de: -Uso general -Datos -Direcciones -Códigos de condición Los registros de uso general no tienen una función especifica, como contener el operando de un código de operación, o usarse como direccionamiento. Los registros de datos solo contienen datos y no pueden usarse para ningún tipo de calculo, mientras que los registros de direcciones pueden usarse como pseudo registro de uso general. Por ultimo están los registros de código de condición, estos son parcialmente visibles para el usuario. Estos registros, como el nombre dice, contiene códigos de condición o flags. Estos son bits que fija el microprocesador después de hacer una operación, por ejemplo una operación puede resultar en un numero positivo, negativo, cero o en un desbordamiento sobre pasar la capacidad del registro o memoria. Después este código o flag especifico puede ser usado en un salto condicional. Estos bit pueden ser leídos, pero no alterados directamente por el programador. ------------------------------------------------------------------------------------------------------------------------------------------- Registros de control y estado Estos registros generalmente no son visibles por el usuario, aunque lo pueden ser si se usa una instrucción en modo núcleo. Estos registros son usados por el microprocesador para, por ejemplo, ejecutar una instrucción: Contador de programa: Contiene la dirección de la siguiente instrucción. Registro de instrucción: Contiene la instrucción captada mas reciente. Registro de dirección de memoria: Contiene una dirección de memoria. Registro intermedio de memoria: Contiene la palabra de datos a escribir en la memoria, o la palabra de datos leída mas reciente. Generalmente los microprocesadores contiene un registro especial, llamado PSW o palabra de estado del programa, que contiene los códigos de condición antes mencionados. Algunos de los códigos de condición que puede contener son: -Signo: Contiene el bit según el resultado de la ultima operación. Por ejemplo 1 para un resultado negativo. -Cero: Contiene un uno cuando el resultado de la ultima operación dio como resultado un cero. -Acarreo: Contiene un uno si la ultima operación tuvo acarreo de bit. -Igual: Contiene uno si el resultado de la comparación es igualdad. -Desbordamiento: Indica un desbordamiento aritmético.
[Expand Post] -Interrupciones habilitadas/deshabilitadas: Habilita o inhabilita las interrupciones. -Supervisor: Indica si el procesador esta trabajando en modo núcleo o no. ------------------------------------------------------------------------------------------------------------------------------------------- Entonces se llega a la conclusión de que los registros ayudan no solo a calcular, ya que pueden y son usados como una memoria auxiliar, sino que también son muy importantes para la ejecución y control del programa. Sin los registros sería lo mismo que hacer cálculos sin lápiz y papel, lo que también implica una perdida de la eficiencia del microprocesador. ------------------------------------------------------------------------------------------------------------------------------------------- Fuentes "Organización y arquitectura de computadores" de William Stallings, 7 edición, capitulo 12 - Estructura y funcionamiento del procesador. ISBN 10: 84-8966-082-4 - ISBN 13: 978-84-8966-082-3
Por cierto Negros informáticos, les voy a dejar los dos libros que les mencione sobre organización y estructura de computadoras. "Computer Organization and Design" no lo pude subir por que pesa 198 Mb. "Computer Organization and Design. The HardwareSoftware" -- David A. Patterson, Morgan Kaufmann: https://annas-archive.org/md5/4029a57c779cbed199136b42e553b5ae
>>761¿Cómo se representan los números mayores que 255? Si se quiere representar números mas grandes que 255, el numero mas alto que se puede representar con un byte 2 posibles dígitos elevado a ocho bit, menos uno por que se empieza a contar desde cero, solo se usan combinaciones de bytes. Por ejemplo: 1 byte: 0 -> 255 2 bytes: 0 -> 65535 3 bytes: 0 -> 16777215 4 bytes: 0 -> 4294967295 • ¿Qué tamaño tienen los registros de las máquinas que vamos a utilizar? Los registros de la arquitectura x86 tienen un tamaño de 4 bytes. • ¿Cómo sabe un ordenador cómo interpretar un determinado byte o conjunto de bytes de memoria? La forma en que el procesador diferencia un dato de una instrucción es según si la palabra de memoria es apuntada, ósea esta almacenada la dirección de memoria, en el puntero de instrucción. • ¿Cuáles son los modos de direccionamiento y para qué se utilizan? Los modos de direccionamiento se usan para saber como se va a acceder al dato o instrucción en memoria, se presento seis tipos de direccionamiento >>760: Inmediato De registro Director Indexado Indirecto Por puntero base • ¿Qué hace el puntero de instrucción? Este registro, contador de programa, indica la ubicación en memoria de la siguiente instrucción a ejecutar por el procesador.
Usa los conceptos¿Qué datos utilizarías en un registro de empleados? ¿Cómo los distribuirías en memoria? Creo que al decir distribuir en memoria hace referencia al valor máximo de tamaño que le asignaría a cada dato. Depende la información que se busque almacenar del empleado, pero como base almacenaría: -Nombre -> 35 bytes (35 caracteres) -Apellido -> 50 bytes (50 caracteres) -Celular -> 15 bytes -DNI -> 15 bytes -Edad -> 5 Bytes -Fecha de nacimiento -> 10 bytes -Dirección -> 75 bytes -Telefono 15 bytes -Mail privado -> 50 bytes -Mail de empresa -> 50 bytes • Si tuviera el puntero al principio del registro de empleados anterior y quisiera acceder a un dato concreto de su interior, ¿Qué modo de direccionamiento utilizaría? Los métodos mas convenientes son el indexada, o el puntero base. • En el modo de direccionamiento por puntero base, si tienes un registro con el valor 3122 y un offset de 20, ¿a qué dirección estarías intentando acceder? valor base: 3122 offset: 20 dirección a la que se accede: • En el modo de direccionamiento indexado, si la dirección base es 6512, el registro índice tiene un 5 y el multiplicador es 4, ¿a qué dirección estaría intentando acceder? valor base: 6512 valor índice: 5 valor multiplicador: 4 dirección accedida: • En el modo de direccionamiento indexado, si la dirección base es 123472, el registro índice tiene un 0 y el multiplicador es 4, ¿a qué dirección estarías intentando acceder? valor base: 123472 valor índice: 0 valor multiplicador: 4 dirección accedida: • En el modo de direccionamiento indexado, si la dirección base es 9123478, el registro índice tiene un 20 y el multiplicador es 1, ¿a qué dirección estarías intentando acceder? valor base: 9123478 valor índice: 20 valor multiplicador: 1 dirección accedida:
Capitulo 3. Tu primer programa Con este capitulo vas a aprender el proceso de escribir y compilar programas en lenguaje ensamblador de Linux. También aprenderás la estructura de un programa ensamblador y algunos comandos de este. Mientras se avanza en el capitulo, puede ser útil consultar el apéndice B y F. Estos programas al principio pueden ser densos, pero es necesario que los leas y releas, incluyendo las explicaciones, todas la veces que necesites. También debes probarlos de todas las formas que creas posibles.
Comenzando con el programa. El primer programa es simple, tan solo va a finalizar. Es corto, pero muestra conceptos basicos del lenguaje ensamblador y la programación Linux. Tienes que escribir en un editor de texto, recomendable Vim o GVim y guardarlo con el nombre de archivo exit.s. El programa es el siguiente: #PURPOSE: # Programa simple que finaliza y devuelve un # código de status al kernel, nucleo de # sistema operativo, de Linux. #INPUT(ENTRADA): none # #OUTPUT(SALIDA): Retorna un código de status. Puede ser visto # escribiendo en la consola # # echo $? # # después de ejecutar el programa. # #VARIABLES: # %eax contiene el número de llamada del sistema # %ebx contiene el estado de retorno # .section .data .section .text .globl _start _start: movl $1, %eax # este es el número de comando del kernel Linux # (llamada al sistema) para salir de un programa movl $0, %ebx # Este es el número de estado que devolveremos al # sistema operativo. Cambiando esto se devolverán # cosas diferentes a echo $? int $0x80 # Esto le avisa al kernel que debe # ejecutar el comando de salida # de programa Lo que escribiste se llama código fuente. Este código es la forma entendible para los humanos de un programa. Para transfórmalo en un programa que una computadora pueda ejecutarlo, hay que ensamblarlo y enlazarlo.
Primero se ensambla. Este proceso transforma los comandos anteriores en instrucción que la maquina pueda comprender. La computadora solo entiende conjuntos de números, por lo que el lenguaje ensamblador es mas próximo a los humanos que las computadoras. Para ensamblar el programa debes ejecutar en la consola de comandos: as exit.s -o exit.o as es el comando para ejecutar el ensamblador. exit.s es el archivo fuente y -o exit.o le indica al ensamblador que coloque el archivo de salida en el archivo exit.o, el cual es un archivo objeto. Un archivo objeto es un archivo que esta en código maquina, pero todavía no se termino de crear como un ejecutable como tal. En los programas extenso tendrás mas de un archivo, el enlazador o linker se encarga de juntar estos archivos y agregar la información que el Kernel necesita para cargar y ejecutar el programa. Como en este caso solo hay un archivo, el enlazador solo agrega la información para poder ejecutarlo. Para enlazar un archivo, hay que ejecutar: ld exit.o .o exit ld es el comando para ejecutar el enlazador, exit.o es el archivo que se quiere enlazar y -o exit le indica al enlazador que debe colocar el programa en el archivo llamado exit. Cuando se modifica un programa, o incluso un comando, hay que re-ejecutar estos dos comandos ensamblador y enlazador. Se puede ejecutar el programa exit con el comando ./exit. El ./ le dice al Kernel que el programa no esta en otra carpeta o directorio, sino en la carpeta actual. Cuando se ejecuta el programa exit, solo finaliza, sin embargo, si después de finalizado el programa ejecutas: echo $? va a dar como salida un 0. Esto pasa por que todos los programas que finalizan le indican al Kernel un código de status, el cual puede indicar que todo se ejecuto correctamente, o que hubo un error. Si el código de status es diferente a 0, indica que hubo un error, una advertencia o un estado diferente a 0. El programador determina lo que cada numero significa. <En la siguiente sección vamos a ver cada parte del código.
Estructura de una programa assembly Al principio del programa, las líneas que empiezan con numeral # son comentarios. Estos comentarios no son traducidos por el ensamblador. Son usados por el programador para "hablar" con cualquier otra persona, o el mismo, que lea el código. El habito de escribir buenos comentarios en él código va a ayudar a entender tanto el como funciona como el por que existe el programa. El libro recomienda que siempre incluyas los siguientes comentarios: • El propósito del código • Una visión general del proceso • Cualquier cosa rara que haga tu programa y por que la hace. Después de los comentarios, esta la línea .section .data Cualquier cosa que empiece con un punto, no es traducido directamente en lenguaje maquina, sino que es una instrucción para el ensamblador. Existen las llamadas directivas de ensamblador o pseudo-operaciones por que las ejecuta el ensamblador, pero no son ejecutadas por la computadora. El comando .section separa el código en secciones. Este comando empieza la sección de datos, donde se lista cualquier almacenamiento de memoria que se vaya a usar. Después de esta sección, aparece otra sección: .section .text que empieza la sección de texto. La sección de texto de un programa es donde están las instrucciones del programa.
La siguiente instrucción es .globl _start Esta instrucción le indica al ensamblador que es importante recordad _start. _start es un símbolo, lo que significa que va a ser reemplazado durante el ensamblado o enlace. Generalmente son usados para marcar ubicaciones en el programa o datos, para que los puedas referenciar por este nombre y no por su numero de ubicación o dirección de memoria. .globl significa que el ensamblador no descartara este símbolo después de ensamblar, por que el enlazador lo va a necesitar. _start es un símbolo que siempre necesita marcarse con .globl por que indica el inicio del programa. Si no se marca esta ubicación de esta forma, la computadora cuando cargue el programa no entenderá por donde debe empezar a ejecutar el programa. La siguiente línea _start: define el valor de la etiqueta _start. Una etiqueta es un símbolo seguido por dos puntos. Las etiquetas definen el valor del símbolo. Cuando el programa es ensamblado, se le asigna a cada dato e instrucción una dirección. Las etiquetas le indican al ensamblador que el valor del símbolo esté donde vaya a estar la siguiente instrucción, ósea que la etiqueta almacené la dirección de la próxima instrucción a la misma etiqueta. Por eso, si la dirección de memoria de un dato o instrucción cambia, no se tiene que reescribir la dirección, sino que el símbolo la va a obtener automáticamente.
Ahora, las instrucciones de computadora. La primera instrucción es movl $1, %eax Cuando el programa la ejecuta, se transfiere un numero 1 al registro %eax. En lenguaje ensamblador, muchas instrucciones tienen operadores. movl tiene dos, la fuente u origen y el destino. En este caso la fuente u origen es el numero 1 y el destino es el registro %eax. Los operandos pueden ser un numero, dirección de memoria o registros. <Vea el Apéndice B para mas información sobre las instrucciones. En la mayoría de las instrucciones que tienen dos operandos, el primero es la fuente u origen y el segundo el destino. En este caso, el operando fuente u origen no es modificado. Otras instrucciones, como addl, subl y imull/ suma, resta y multiplicación tampoco lo modifican, solo hacen el calculo y lo guardan en el operando destino. Otros, son mas complicados. Como dice el libro, idivl división entera larga necesita que el dividiendo se ubique en %eax, mientras que %edx tiene que estar en cero por que va a ser el lugar donde se almacene el resto de la división, pero el divisor puede ser cualquier registro o posición de memoria. En los procesadores x86 hay varios registros de uso general: • %eax • %ebx • %ecx • %edx • %edi • %esi Pero también hay varios registros de uso especial, incluyendo: • %ebp: Puntero base • %esp: Puntero a pila • %eip: Puntero de instrucción • %eflags: Registros de flags Algunos de estos registros solo se pueden acceder desde instrucciones especiales, como %eip. Otros se pueden acceder como si fueran registros de propósito general, pero todos tienen un uso especial, o son mas rápidos si se usan de una forma especifica.
Entonces, la instrucción movl mueve el numero 1 dentro del registro %eax. El símbolo de billete o monetario $ delante del uno indica que se va a usar el modo inmediato de direccionamiento. Sin este símbolo, haría un direccionamiento directo, cargando cualquier numero que este en la dirección uno. La razón de haber movido un numero 1 dentro de %eax es porque se esta preparando para llamar al Kernel de Linux. El numero 1 es el numero de llamada de salida del sistema. Las llamadas al sistema se va discutir mas adelante, pero son solicitudes para obtener la ayuda del sistema operativo. Por lo general el programa no puede hacer todo, tareas como manejar archivos, comunicarse con otros programas o finalizar la ejecución son manejados por el sistema operativo a través de llamadas al sistema. Cuando vas a hacer una llamada al sistema, el numero de la llamada debe ser cargador en %eax. Según la llamada al sistema, otros registros pueden necesitar tener valores almacenados. Aunque, las llamadas al sistema no son el uso principal ni el único uso de los registros. <Para una lista completa de las llamadas al sistema y su numero, ver Apéndice C Usualmente, el sistema operativo necesita mas información. Por ejemplo, cuando se va a salir de un programa, necesita saber el código de estado, el cual debe ser cargado en %ebx y este es el valor que se devuelve cuando se ejecuta echo $?. Esta información extra que necesita el sistema operativo son parámetros. Entonces, para cargar el registro %ebx con un cero, se usa la siguiente instrucción: movl $0, %ebx Pero, no solo es necesario haber cargado el registro con este numero para finalizar el programa, esto no hace nada ya que los registros se suelen usar para varias cosas aparte de llamadas al sistema, como sumas o comparaciones.
La siguiente instrucción es la que hace la magia: int $0x80 El int es por interrupt interrupción. El numero 0x80 es el numero de interrupción a usar. Una interrupción interrumpe el flujo normal del programa, transfiere el control del programa a Linux para que puede hacer la llamada al sistema. Una vez que la interrupción termina, como programador no te importa como funciona sino que funciona, Linux devuelve el control al programa Revisión rápida de las llamadas al sistema: Para recapitular, se accede a las características del Sistema Operativo a través de llamadas al sistema. Éstas se invocan configurando los registros de una manera especial y emitiendo la instrucción int $0x80. Linux sabe a qué llamada del sistema queremos acceder por lo que almacenamos en el registro %eax. Cada llamada al sistema tiene otros requerimientos en cuanto a lo que necesita ser almacenado en los otros registros. El número de llamada al sistema 1 es la llamada al sistema de salida (exit), la cual requiere que el código de estado sea colocado en %ebx. Ahora, el libro recomienda hacer pequeñas modificaciones al programa, como cambiar el numero cargado en %ebx. También da una pequeña recomendación, "No te preocupes, la peor cosa que podría pasar es que el programa no se ensamble o enlace, o que se congele la pantalla. Es todo parte del aprendizaje.".
Anon sigue aprendiendo assembly? section .data text db 'Hello, world!', 10 section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, text mov rdx, 14 syscall mov rax, 60 mov rdi, 0 syscall
>>804 Perdón negro, estuve ocupado. Esta noche seguiré con el hilo.
Planeando el programa En el siguiente programa vamos a tratar de encontrar el máximo de una lista de números. Las computadoras necesitan instrucciones muy detalladas, por lo que habrá que planear en detalle lo que hará el programa, lo que incluye: • ¿Dónde se va a almacenar la lista de números? • ¿Qué proceso va a necesitar para encontrar el número máximo? • ¿Cuánto espacio necesitamos para almacenar las instrucciones del proceso? • ¿Todo el almacenamiento va a caber en los registros, o se necesita usar memoria? Puedes pensar que algo tan simple como encontrar el numero máximo de una lista no puede tomar tanta planeación. Puedes decirle a las personas que encuentren el numero máximo, lo harán sin problema. De todas formas, nuestras mentes están hechas para hacer varias tareas complejas automáticamente. En cambio las computadoras necesitan ser instruidas a través del proceso. También podemos guardar cualquier numero de cosas en nuestra mente sin mucho problema, incluso lo hacemos sin darnos cuenta. Por ejemplo, si escaneas una lista de números buscando el máximo, lo mas probable es que mantengas en mente dos cosas, el numero mas grande que encontraste hasta el momento, y la ubicación de este numero en la lista. Mientras tu mente hace esto automáticamente, con las computadoras tienes que ser explicito en especificar el almacenamiento para guardar el máximo numero y su ubicación. Puedes tener otros problemas, por ejemplo, cuando parar. Cuando lees un pedazo de papel, puedes detenerte cuando te quedas sin números. Pero la computadora solo contiene números, por lo que no tiene idea de cuando llego a lo ultimo de la lista.
En las computadoras, tienes que pensar todos lo que la computadora debe hacer. Supongamos que el nombre de la dirección donde la lista de numeros empieza es data_items. También, el ultimo numero de la lista va a ser cero, por lo que ya se sabe donde debe parar el programa. También es necesario un valor para fijar la posición actual en la lista, otro para el valor que se va a examinar y un ultimo valor para guardar el mayor numero: • %edi guarda la posición actual en la lista • %ebx guarda el máximo numero • %eax guarda el numero que se esta examinando El programa empieza por examinar el primer numero de la lista, y como acaba de empezar, ese numero va a ser el mayor numero, por lo que se tiene los siguientes pasos: 1. Verificar si el numero actual (%eax) es cero. 2. Si lo es, el programa termina. 3. Incrementa la posición actual en uno (%edi). 4. Carga el siguiente numero en la lista en el registro de valor actual (%eax). 5. Compara el numero actual (%eax) con el mayor numero (%ebx). 6. Si el numero actual es mayor, se reemplaza el numero guardad en el registro de mayor numero por el numero actual. (%eax) --->>> (%ebx) 7. Repetir.
Dependiendo si la condición del 'si' 'if' es correcta, el programa tomara un camino u otro. Por ejmplo, si en el primer paso el numero actual es igual a cero entonces se ejecuta el paso dos el programa termina, sino salta al paso tres. Esos 'si' 'if' en los lenguajes de programación son instrucciones de control de flujo, por que le dice a la computadora, según una condición, que instrucción seguir. Esto se logra gracias a dos instrucciones diferentes, el salto condicional y el salto incondicional. El salto condicional cambia el rumbo en base al resultado de una comparación o calculo previo. En cambio, el salto incondicional salta directamente a un camino diferente. Puede parecer que es inútil, pero es necesario en muchos casos. Por ejemplo, es útil cuando se debe volver al rumbo principal del programa. Otro uso del control de flujo es la implementación de bucles. Un bucle es una parte de un programa que se repite, puede implementarse con saltos incondicionales al comienzo del bucle en el final de este. También es importante establecer un salto condicional para salir de este bucle, o sino se convierte en un bucle infinito. En la siguiente sección se va a implementar el programa como fue planeado. Parece complicado, y en cierto punto lo es, pero cuando comienzas a programar es difícil convertir los pensamientos normales en un proceso que la computadora pueda entender.
>>804 Anon, ¿Puedes explicarme ese programa? ¿Cuál ensamblador usas?
>>811 Es del segundo vid de esta lista https://www.youtube.com/playlist?list=PLXNR8A6QBzobli2v8QoXkJVpQWNx_fH-1 Para ser honesto solo entiendo que es un hola mundo en x_64 y poco más. Desde ese día no he avanzado porque primero quiero aprender c y cosas de estructura de datos y algoritmos, es decir programación como dios manda. De todas formas mi objetivo es aprender asm y entender el funcionamiento de la máquina. Also libros que he encontrado de asm x_64 sobre gnu/linux >Ed Jorgensen - x86-64 Assembly Language Programming with Ubuntu (2020) >Ray Seyfarth - Introduction to 64 Bit Intel Assembly Language Programming for Linux (2011) >Jeff Duntemann - x64 Assembly Language Step-by-Step: Programming with Linux (2023)
>>812 Estaba pensando, como mencione antes hacer un hilo sobre el libro "Introducción a los algoritmos", tercera edición de Cormen, Leiserson, Rivest y Stein. Podría crearlo y con suerte aumente el trafico en >>>/hispatec/. También es buena idea, como menciono un negro, tener un hilo sobre programación diaria.
>>815 ¿Y si abre un general de programación para que sirva de centro de operaciones desde donde gestionar hilos relacionados? Por cierto, de ese libro hay una cuarta edición en libgen.
Encontrando el numero máximo Escribe el siguiente programa como maximum.s: #PURPOSE: Este programa encuentra el maximo numero de # un conjunto de numeros. # #VARIABLES: Los registros tiene el siguiente uso: # # %edi - Guarda el indice del numero que se esta comparando actualmente # %ebx - El mayor numero encontrado # %eax - El numero actual que se esta comparando # # Se usan las siguientes ubicaciones de memoria: # # data_items - Contiene la lista. Se usa un 0 # para senialar el final de la lista # .section .data data_items: #Estos son los números a analizar .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0 .section .text .globl _start _start: movl $0, %edi # Mueve un cero al registro indice movl data_items(,%edi,4), %eax # Carga el primer byte de datos movl %eax, %ebx # como este es el primer numero, %eax es # el mas grande start_loop: # empieza un bucle cmpl $0, %eax # verifica si llego al final je loop_exit incl %edi # incrementa en indice en uno movl data_items(,%edi,4), %eax # carga el elemento en esa posicion en %eax cmpl %ebx, %eax # compara los valores jle start_loop # salta al principio del bucle si el numero # no es mayor movl %eax, %ebx # Carga el nuevo numero como el mayor jmp start_loop # Salta al principio del bucle loop_exit:
[Expand Post] # %ebx es el codigo de estado para la llamada al sistema de salida # y ya tiene el maximo numero movl $1, %eax #1 es la llamada al sistema de salida int $0x80 Entonces, ensambla y enlaza con estos comandos: as maximum.s -o maximum.o ld maximum.o -o maximum Ahora ejecuta el programa y verifica la salida. ./maximum echo $?
Este programa va a devolver el numero 222. En la sección de datos esta la etiqueta data_items que referencia a la ubicación que le sigue, ósea, la sección de memoria que guarda la lista a analizar. Después hay una instrucción que empieza con .long. Esta instrucción sirve para que assembly reserve memoria para la lista de números que le sigue. Algo muy importante y a tener en cuenta es que data_items referencia a la ubicación del primer elemento o numero de la lista. Hay muchas mas instrucciones del tipo .long Voy a buscar mas información sobre estas directivas, y si encuentro lo posteo.: .byte Reserva memoria para un byte, soporta números entre 0 a 255. .int Reserva dos ubicaciones por número, por lo que soporta números entre 0 a 65535. .long Reserva cuatro ubicaciones de memoria por numero. Son del mismo tamaño que los registros, por lo que pueden soportar números entre 0 y 4294967295 .ascii Sirve para guardar en memoria caracteres o cadenas de caracteres, como dice el libro, si tienes la directiva .ascii "Hello World!\0", entonces reservara 12 ubicaciones. \0 es un carácter de escape que indica el final de la cadena. El autor hace una mención de los métodos para que el bucle termine. Tanto haber hard-codeado el tamaño de la lista, como crear un símbolo que marque el final de la lista son posibles soluciones. Pero a lo que quiere resaltar es que la computadora no sabe, solo lo que el programador dice. data_items no tiene la declaración o modificador .globl por que es una ubicación que solo se usa dentro de ese programa, no en otros archivos. Al contrario que con el símbolo _start, por que en este caso es necesario para que Linux conozca por donde debe empezar la ejecución del programa. Por lo que no es un error agregar la declaración .globl a _start, solo es innecesario por que no se esta usando esa etiqueta en otros archivos.
Una variable es una ubicación de memoria usada para un propósito especifico, con un nombre dado por el programador. Por ejemplo, el programa de encontrar el mayor numero tiene tres variables, de la cuál una es usada para almacenar el mayor numero encontrado. Hay casos en que las variables, por ser mas que los registros, deben guardarse en memoria y, al momento de usarse, después cargarse en los registros. Esto es algo que se va a ver mas adelante. Los índices, en este caso de la lista de números, son la posición en la que se encuentra cada numero. El primer elemento siempre tiene índice cero y a medida que se avanza por la lista, el índice incrementa en uno. Por eso mismo, al usar el registro %edi como índice, se lo carga con el valor 0. La siguiente instrucción es importante: movl data_items(,%edi,4), %eax Para entender esta instrucción hay que tener en cuenta que: • data_items es la ubicación en memoria del comienzo de la lista de los números No es la lista, solo el primer elemento • Cada elemento tiene, para su almacenamiento, 4 ubicaciones de memoria, por que se declaro que los elementos de la lista son de tipo .long%edi esta almacenando 0 en este punto del programa. Básicamente, lo que esta instrucción dice es "comienza en el principio de data_items, y toma el primero elemento, y recuerda que cada elemento tiene 4 ubicaciones de memoria para su almacenamiento. Entonces, guarda ese elemento de la lista en %eax". La forma general es la siguiente: movl BEGINNINGADDRESS(,%INDEXREGISTER,WORDSIZE) Como %eax esta almacenando en este momento al primer numero, es el mayor numero que encontró el programa, por lo que se lo copia en %ebx
Acá empieza lo interesante, ahora estamos dentro de un bucle. Un bucle es un segmento del programa que se va a ejecutar mas de una vez. Se marco el comienzo del bucle con el símbolo start_loop. Un bucle se usa siempre que se vaya a repetir un fragmento del código, se sepa o no la cantidad de veces que se vaya a repetir. Cuando el código alcanza el final del bucle, pero la condición para salir de este no es verdadera, se vuelve al principio del bucle. Ósea, se vuelve al símbolo start_loop. Después del comienzo del bucle esta el siguiente fragmento de código. cmpl $0, %eax je loop_exit La instrucción cmpl compara dos valores, en este caso el numero cero con el numero almacenado en %eax. Esta comparación afecta a un registro que no se había mencionado en el programa, el registro %eflags, también conocido como registro de estatus, tiene mucho usos, pero el que vamos a ver ahora es donde se almacena el resultado de la comparación en este registro. La siguiente línea es un control de código, que dice que el programa debe saltear al símbolo loop_exit si los valores antes comparados son iguales. Eso es lo que la e de la instrucción je significa. Hay muchas variantes de la instrucción jump: je:Salta si los valores comparados son iguales jg:Salta si el segundo valor es mas mayor que el primero jge:Salta si el segundo valor es mayor o igual al primero jl:Salta si el segundo valor es menor que el primero jle:Salta si el segundo valor es menor o igual al primero jmp:Salta no importa que, por lo que no necesita estar precedido por una comparación La lista completa esta documentado en el Apéndice B. En el caso del programa, salta al símbolo loop_exit si el valor que almacena %eax es igual a cero.
Siguiendo con el programa, si el ultimo numero cargado no es cero, entonces se sigue con estas instrucciones: incl %edi movl data_items(,%edi,4), %eax En este caso, la instrucción incl se encarga de incrementar en uno el valor de %edi, entonces copia el elemento de la lista que este en la posición indicada por %edi en el registro %eax, ahora este registro tiene el siguiente valor a ser testeado. cmpl %ebx, %eax jle start_loop Aca compara el valor almacenado en el registro %eax con el mayor numero encontrado, almacenado en %ebx. Si el mayor numero encontrado es mayor o igual al numero actual, salta al principio del bucle. Si, por el contrario, el mayor numero es menor al numero actual, entonces se debe guardar en %ebx y saltar al principio del bucle. movl %eax, %ebx jmp start_loop Entonces, el bucle se ejecuta hasta que alcanza un valor cero, en ese momento salta al símbolo loop_exit. En esta parte del programa, se llama al kernel de Linux para terminar la ejecución del programa. Como se menciono antes, para salir del programa se carga un 1 en el registro %eax y se especifica el estatus de salida en el registro %ebx. Como se quiere retornar el mayor numero de la lista, no se toca ese registro: movl $1, %eax int 0x80
instrucciones movl origen, destino: Copia un valor desde origen a destino. cmpl operando1, operando2: Compara los dos operandos y actualiza el registro eflags. incl operando: Incrementa a operando en uno. addl origen, destino: Suma el valor del operando fuente al operando de destino y almacena el resultado en el operando de destino. subl origen, destino: resta el valor del operando fuente al operando de destino y almacena el resultado en el operando de destino. imull origen, destino: hace una multiplicación con signo y guarda el resultado en destino. divl divisor: hace una división sin signo, divide el contenido de la doble palabra contenida en %edx:%eax por el registro o ubicación de memoria especificado. El registro %eax guarda el cociente resultante y el registro %edx el resto resultante. Si el cociente es demasiado grande para %eax, se activa una interrupción de tipo 0. idivl divisor: trabaja igual que divl, pero hace una división con signo. int numero: causa una interrupción según numero. Registros Uso general %eax %ebx %ecx %edx %edi %esi Uso especial %eflags: Registro de estatus Reserva de memoria .byte: Reserva memoria para un byte, soporta números entre 0 a 255. .int: Reserva dos ubicaciones por número, por lo que soporta números entre 0 a 65535. .long: Reserva cuatro ubicaciones de memoria por numero. Son del mismo tamaño que los registros, por lo que pueden soportar números entre 0 y 4294967295 .ascii: Sirve para guardar en memoria caracteres o cadenas de caracteres, como dice el libro, si tienes la directiva .ascii "Hello World!\0", entonces reservara 12 ubicaciones. \0 es un carácter de escape que indica el final de la cadena. Saltos Saltos condicionales je:Salta si los valores comparados son iguales jg:Salta si el segundo valor es mas mayor que el primero jge:Salta si el segundo valor es mayor o igual al primero jl:Salta si el segundo valor es menor que el primero jle:Salta si el segundo valor es menor o igual al primero Saltos incondicionales jmp:Salta no importa que, por lo que no necesita estar precedido por una comparación
Modos de direccionamiento En la sección Métodos de acceso a datos en el capitulo se mostro diferentes tipos de métodos de direccionamiento disponibles para usar en ensamblador. En esta sección se va a mostrar como estos modos son representados en las instrucciones de ensamblador. La forma general de referencias a direcciones de memoria es esta: ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER) Todos esos campos son opcionales, para calcular la dirección simplemente se calcula de es esta forma: FINAL ADDRESS = ADDRESS_OR_OFFSET + %BASE_OR_OFFSET + MULTIPLIER * %INDEX ADDRESS_OR_OFFSET y MULTIPLIER deben ser constantes, mientras que los otros dos deben ser registros. si alguna de esas partes se excluye, simplemente se reemplaza con un cero en la ecuación. Todos los métodos de direccionamientos mostrados en el capitulo 2, excepto el modo inmediato, puede ser representado de esta forma.
Modo de direccionamiento directo En este caso, solo se usa la parte ADDRESS_OR_OFFSET. Por ejemplo: movl ADDRESS, %eax Esto carga %eax con el valor que esta en la posición ADDRESS. Modo de direccionamiento indexado En este, solo se usa las partes ADDRESS_OR_OFFSET e %INDEX. Se puede usar cualquier registro de propósito general como el registro index. También se puede agregar un multiplicador constante de 1, 2 o 4 al registro índice, para hacer mas fácil el indexado de bytes, bytes dobles y palabras. Por ejemplo, se tiene una cadena de bytes como string_start y se quiere acceder a la tercer (un índice de 2 ya que empezamos a contar el índice en cero), y %ecx contiene el valor 2, si se quiere cagar este valor en %eax se podría hacer de esta forma: movl string_start(,%ecx,1), %eax Método indirecto El metodo indirecto carga un valor de la dirección indicada por un registro. Por ejemplo, si %eax contiene una dirección, se podría mover el valor de esa dirección a %ebx haciendo lo siguiente: movl (%eax), %ebx
Método de direccionamiento con puntero base Este direccionamiento es similar al direccionamiento indirecto, excepto que agrega un valor constante a la dirección en el registro. Por ejemplo, si se tiene un registro donde el valor de la edad ocupa 4 bytes, y se tiene la dirección del registro en %eax, se puede carga el valor de edad en %ebx usando la siguiente instrucción: movl 4(%eax), %ebx Método inmediato El método inmediato es muy simple, no sigue la forma general. Este método es usado para cargar valores directos en los registros en la memoria. Por ejemplo, si se quiere cara el numero 12 en %eax, solo hay que hacer lo siguiente: movl $12, %eax Para indicar que se va a usar el método inmediato se usa el símbolo de dinero en frente del numero. Si no se usara, se toma como el modo de direccionamiento directo. Método de direccionamiento de registro Este modo solo mueve datos dentro o fuera de los registros, este método se uso en todos los anteriores ejemplos para la otra parte del operando.
Estos métodos de direccionamiento son muy importantes, por que cualquier acceso a memoria va a utilizar uno de estos. Todos estos métodos pueden ser usados como operando de destino u origen, a excepción del método inmediato, este método solo puede ser usado como operando de destino. Existen diferentes instrucciones para los diferentes tamaños de valores que se quiera mover. Por ejemplo movl se usa para mover una palabra, en cambio movb se usa para mover un byte. Encontré estas posibles variantes del operando mov: movb: Indica un byte (8 bits). movw: Indica una palabra (16 bits). movl: Indica un valor de doble palabra (32 bits). movq: Indica un valor de cuatro palabras (64 bits) También se puede dividir a los registros. Por ejemplo el registro %eax almacena una palabra, pero si solo se necesita dos bytes se puede usar %ax, justamente la e en %eax significa extended. Si se quiere usar un solo byte se puede usar %al o %ah. En estos casos la l simboliza low y la h simboliza high. Tanto high como low hacen referencia en la significancia del bit. Cabe resaltar que si se tenía un numero almacenado en %eax y se cambia el valor de %al, se va a corromper el valor que esta almacenado en %eax.
Con esto ya se pueden hacer programas interesantes, como el ordenamiento por burbuja. https://es.wikipedia.org/wiki/Ordenamiento_de_burbuja
>>844 Me equivoque, todavía no se puede programar un ordenamiento de burbuja con lo que se explico. Sigamos con el libro.


Forms
Eliminar
Informar
Respuesta rápida