Apuntes primer parcial 03/10/2006 (Organización del Computador II)
Strings
Averigüe el puntero/longitud a la meseta mas larga. Meseta es repetición de letras iguales.
Se anidan dos ciclos/funciones/macros.
Macro interna longMeseta: Dado esi (en strings, puntero al src, en gral), devuelve la longitud de la meseta q empieza en esi.
Requiere:
- esi al comienzo de la meseta
- bit de direcciones en avanzar
Asegura:
- esi en el caracter siguiente a la meseta
- ecx tiene la longitud del resto de la cadena
- ebx es la longitud de la meseta
Se puede hacer mediante la instrucción scas. Se usa un lodsb para levantar el 1er byte de la meseta, el scas para iterar, y un repe para iterar mientras sea igual. La longitud de la meseta es la diferencia entre el esi del final y el esi del ppio.
Se debe llamar a longMeseta para recorrer toda la cadena, en un ciclo externo.
while ecx > 0 longMeseta ebx = max(ebx, max_actual)
Instrucción LEA
lea reg, [reg1 + reg2*t + k]
Tiene el mismo formato que un modo de direccionamiento Donde t es 1,2,4,8; k es un inmediato de 32 bits; y los registros son de 32 bits. Nota importante: No modifica las flags, a veces es util usar lea en lugar de add cuando se quieren preservar las flags.
Pila
El push decrementa el esp, el pop incrementa. Nunca hay que pushear/popear registros o datos que no sean de 32 bits. Todas las variables al escribir un procedimiento deben ser locales y encontrarse en el stack. Las variables globales, con section data, son solo para programas (no se hace en el parcial!).
Números grandes
Para trabajar con enteros grandes, se pueden hacer suposiciones sobre el tamaño máximo de los mismos que surjan del contexto de uso. No hay que validar de más en assembler, la idea es funciones rapidas.
Multiplicación de números grandes por potencias chicas de 2
Sea el numero 08 00 CA FE Lo consideramos "grande" para este ejercicio.
Lo queremos multiplicar por 2 En el primer byte uso un SHL/SAL 1 En los siguientes, RCL, rotate left circular
El shift saca el bit más significativo y lo mete en el CF. El RCL hace un shift, mete el CF en el menos significativo, y pisa el CF con el más significativo q saco afuera. El ROL pasa directamente del más significativo al menos, y copia el más significativo al CF.
El SHR mete un cero en el más significativo, y el SAR repite el 7mo bit. Tiran el bit menos significativo que sacaron en el CF. Para dividir hay que ir del dword más significativo al menos.
Para multiplicar por 4, no conviene correr 2 veces el algoritmo anterior por los accesos a memoria. Como se necesitarian 2 carry flag, se puede ir tirando los bits del carry a otro registro, haciendo RCL del reg que tiene el número para cargar el carry, después otro RCL contra el reg adicional para guardar ahí el carry, y así sucesivamente. Antes de seguir a la proxima dword, se shiftea lo necesario para pasar los bits guardados de la parte más alta a la más baja. Otra posibilidad es guardar los bits más significativos con operaciones lógicas: copiar el registro y hacer un and con una máscara que deje solo los más altos que se quieran guardar. Aproximadamente se debería hacer lo siguiente:
xor eax, eax ciclo: mov ebx, [esi] mov edx, ebx and edx, 0xF8000000 ; Máscara con cinco 1s y el resto ceros shr eax, 27 shl ebx, 5 or ebx, eax mov eax, edx loop ciclo
Multiplicar números grandes con signo
Para multiplicar números grandes, primero pasar los dos a positivo y después multiplicar, y ver cual debería ser el signo del resultado y cambiarlo acordemente.
Para cambiar de signo números grandes, empiezo por el menos significativo y voy negando y sumando uno (el carry después de la 1er dword). La negación se puede hacer en los registros, no hace falta negarlo en memoria. Hay que guardar el carry en ese caso para saber qué sumar.
Multiplicar un número grande por un registro de 32bit
Se tiene un número grande en memoria, en esi el puntero. En ebx el número por el que se quiere multiplicar. Cargo la parte baja en eax, multiplico por ebx y queda el resultado en edx:eax. En la 1er iteración, se puede bajar eax directamente. La parte alta, edx, es necesario guardarla (x ej en ecx).
mov eax, [esi] mul ebx mov [edi], eax mov ecx, edx
Luego hay que sumar eax y ecx antes de bajar a memoria. El carry que se produce por sumar ecx y edx hay que pasarlo a edx. Ese pasaje de carry NO puede producir carry, por el tamaño de los números.
(incPunteros) mov eax, [esi] mul ebx add eax, ecx adc edx, 0 mov [edi], eax mov ecx, edx
Convención C
Llamador
Primero, push de los parámetros, del último al primero. La convención C no asegura que los parámetros que se pasan no se modifican; de hecho se pueden usar como locales para la función llamada. No está bien extraer información adicional a partir del estado de los parámetros al retornar. Luego call (pushea instruction pointer, o program counter, y lo cambia a la dirección a la que salta).
Al retornar, el llamador debe hacer un add del esp para liberar el espacio reservado para los parámetros. El resultado está en eax, si no es void. Si es de 64 bits, generalmente está en edx:eax.
Llamado
Push del ebp anterior. Modifica su propio ebp para apuntarlo al tope actual de la pila (mov ebp, esp). No es necesario guardar ebp y usarlo para acceder al stack frame, pero es recomendable. Se puede usar ebp como otro registro más, NO el esp. Se pushean los registros que deben salvarse (ebx, edi, esi), si van a usarse. Si no no es necesario. Se hace sub del esp lo necesario para reservar espacio para las variables locales o los out. Para direccionar a variables locales y a parámetros, se direcciona sumando o restando a ebp. Ej: para ver el 1er parámetro, [ebp + 8].