¿Qué hizo IAR antes de main?
Recientemente quiero escribir un sistema operativo simple en Cortex-M3 y planeo usar IAR. Para escribir bien el código de inicio, dediqué algún tiempo a comprender qué hace IAR antes de main().
Primero, cuando se reinicia el sistema, Cortex-M3 obtiene la dirección superior de la pila del área de código compensada 0x0000'0000 para inicializar el valor del registro MSP.
A continuación, obtenga la dirección de salto de la primera instrucción del área de código compensada 0x0000'0004. Estas direcciones son donde CM3 requiere que se coloque la tabla de vectores de interrupción.
Aquí está el desmontaje del área de inicio de un programa:
__vector_table:
08004000 2600
08004002 2000 p>
08004004 7E1D
08004006 0800
Este programa lo inicia el programa IAP. El programa IAP obtiene el valor de MSP en 0x0800'4000 (0x20002600) y lo establece en. el valor MSP, es decir, el rango máximo de la pila principal es 0x2000'0000~0x2000'25FF. A continuación, el programa IAP obtiene la dirección de Reset_Handler en 0x0800'4004
(0x0800'7E1D) y salta a Reset_Handler() para su ejecución.
IAP aquí imita completamente la secuencia de reinicio de Cortex-M3, es decir, en un sistema sin IAP, CM3 solo puede obtener MSP de 0x0800'0000, de
0x0800'. 0004 obtiene la dirección de la primera instrucción. El IAP existe en la dirección 0x0800'0000. El inicio del IAP ha consumido esta secuencia de reinicio, por lo que cuando IAP quiere iniciar el programa UserApp, también imita completamente la secuencia de reinicio de Cortex-M3.
A continuación, echemos un vistazo a lo que hay en la primera instrucción después del reinicio: la función Reset_Handler().
Si utilizamos la biblioteca de periféricos estándar de la compañía ST, entonces ya existe un Reset_Handler listo para usar, pero es una definición débil: PUBWEAK, que puede ser anulada por nuestra función reescrita del mismo nombre.
En términos generales, utilizamos el Reset_Handler proporcionado por ST. En la versión V3.4 de la biblioteca, esta función se puede encontrar en startup_stm32f10x_xx.s:
PUBWEAK Reset_Handler
SECTION .text. :CODE:REORDER(2)
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start p>
BX R0
Parece que ST no hizo mucho. Solo llamó a la función SystemInit proporcionada por su propia biblioteca para inicializar el reloj del sistema y la lectura de Flash, y entregó el poder.
__iar_program_start, la "función interna" proporcionada por IAR, seguiremos este salto __iar_program_start para ver qué hace IAR
La reacción del código anterior El ensamblaje es el siguiente: p>
Reset_Handler:
__iar_section$$root:
08007E1C 4801 LDR R0, [PC, #0x4]; =SystemInit p>
; 08007E1E 4780 BLX R0;BLX R0
08007E20 4801 LDR R0, [PC, #0x4];LDR R0, =__iar_program_start
08007E22 4700 BX R0;BX R0
08007E24 6C69
08007E26 0800
08007E28 7D8D
08007E2A 0800
Los espectadores atentos encontrarán que la dirección es 0x0800'7E1C 1 peor que el 0x0800'7E1D que encontramos. Este es un problema heredado de la familia ARM, porque las instrucciones del procesador ARM están alineadas al menos con media palabra (conjunto de instrucciones THUMB de 16 bits o
32). conjunto de instrucciones ARM de bits), por lo que el LSB del puntero de la PC es siempre 0. Para hacer un uso completo del registro, ARM le ha dado al LSB de la PC una misión importante, que es realizar saltos de rama en ese momento. , LSB=1 de PC significa usar el modo THUMB, LSB=0 significa usar el modo ARM, pero en el último núcleo Cortex-M3, solo se usa el conjunto de instrucciones THUMB-2 para tomar la iniciativa. entonces
Este bit siempre debe permanecer en 1, por lo que la dirección que encontramos es 0x0800'7E1D (C=1100, D=1101). No te preocupes, nuestro kernel CM3 ignorará el LSB (a menos que lo sea). 0 , entonces
causará una falla), saltando así correctamente a 0x0800'7E1C.
A partir de la instrucción de carga en 0x0800'7E20, podemos calcular la posición de __iar_program_start, que es el puntero actual de la PC
(0x0800'7E24), más 4, que es 0x0800. dirección señalada por '7E28 - 0x0800'7D8D (0x0800'7D8C), saltamos rápidamente
y __iar_program_start está efectivamente aquí:
__iar_program_start:
08007D8C F000F88C BL __low_level_init
08007D90 2800 CMP R0, #0x0
08007D92 D001 BEQ __iar_init$$done
08007D94 F7FFFFDE BL __iar_data_init 2
08007D98 2000 MOVS R0, #0x0
08007D9A F7FDFC49 BL main
Vemos que IAR proporciona la función __low_level_init para la inicialización de "nivel inferior" y un mayor seguimiento. Podemos verificar qué es el __low_level_init La función sí lo hace, y no es tan ulterior como imaginamos.
__low_level_init:
08007EA8 2001 MOVS R0, #0x1
08007EAA 4770 BX LR
__low_level_init es sorprendentemente simple, solo ve Cuando 1 se escribe en el registro R0, "BX
LR" se ejecuta inmediatamente y se devuelve a la ubicación de llamada. Luego, __iar_program_start verifica si R0 es 0. Si es 0, se ejecuta __iar_init$$done. Si no es 0, simplemente
ejecute __iar_data_init2. La función __iar_init$$done es muy simple, con solo 2 oraciones. La primera oración es borrar R0 y la segunda oración es directamente "BL
main" y saltar a la función main(). Pero dado que __low_level_init ha escrito 1 en R0, todavía tenemos que recorrer un largo camino: veamos qué hace __iar_data_init2. Aunque está a solo un paso de main, está oculto en el medio. Tenemos que ser pacientes y mirar el compilador. pensamiento.
__iar_data_init2:
08007D54 B510 EMPUJAR {R4,LR}
08007D56 4804 LDR R0, [PC, #0x10]
08007D58 4C04 LDR R4, [PC, #0x10]
08007D5A E002 B 0x8007D62
08007D5C F8501B04 LDR R1, [R0], #0x4
08007D60 4788 BLX R1
08007D62 42A0 CMP R0, R4
08007D64 D1FA BNE 0x8007D5C
08007D66 BD10 POP {R4,PC}
08007D68 7C78 p>
p>
08007D6A 0800
08007D6C 7C9C
08007D6E 0800
Parece que IAR no ha ejecutado la función main() durante Mucho tiempo solo para ejecutar __iar_data_init2. Analicemos qué cosas malas ha hecho IAR ~
Primero presione R4, LR se inserta en la pila, luego cargue 0x0800'7C78 en R0, 0x0800'7C9C en
R4, salte inmediatamente Vaya a 0x0800'7D62 para realizar la comparación entre R0 y R4. Si los resultados son iguales, abra R4 y PC, y luego ingrese main() inmediatamente.
Sin embargo, IAR no nos dejará salir tan rápido si le pedimos que ingrese a la urna; los resultados no son iguales, salte a 0x0800'7D5C para su ejecución, aquí coloque la dirección señalada por R0: el valor en 0x0800'7C78 ——
0x0800'7D71 se carga en R1 y el valor en R0 se incrementa en 4, se actualiza a 0x0800'7C7C y salta a la dirección señalada por R1 para su ejecución. Aquí hay otra función IAR. p. >
Número: __iar_zero_init2:
__iar_zero_init2:
08007D70 2300 MOVS R3, #0x0
08007D72 E005 B 0x8007D80
08007D74 F8501B04 LDR R1, [R0], #0x4
08007D78 F8413B04 STR R3, [R1], #0x4
08007D7C 1F12 SUBS R2, R2, #0x4
08007D7E D1FB BNE 0x8007D78
08007D80 F8502B04 LDR R2, [R0], #0x4
08007D84 2A00 CMP R2, #0x0
08007D86 D1F5 BNE 0x8007D74 < / p>
08007D88 4770 BX LR
08007D8A 0000 MOVS R0, R0
Antes de que __iar_data_init2 termine de ejecutarse, salta a este __iar_zero_inti2. Analicémoslo lentamente. - __iar_zero_inti2 hazlo.
__iar_zero_inti2 borra el registro R3 e inmediatamente salta a 0x0800'7D80 para ejecutar 'LDR R2, [R0],
#0x4'. Esta instrucción es la misma que se acaba de ver. en __iar_data_init2 El 'LDR R1, [R0],
#0x4' obtenido es muy similar, ambos son "post-índice". Esta vez, la dirección a la que apunta R0 (el valor en 0x0800'7C7C - 0x0000'02F4 se carga en el registro R2 y luego el valor en R0 se incrementa en 4 y se actualiza a 0x0800'7C80. La siguiente instrucción verifica si R2 es 0. Obviamente esta función no es tan simple. El valor de R2 es 2F4 y nos llevan nuevamente a 0x0800'7D74. Las siguientes cuatro instrucciones hacen lo siguiente:
1. Cargue la dirección señalada por R0 - el valor en 0x0800'7C80 - 0x2000'27D4 en el registro R1, luego agregue 4 al valor en R0 y actualícelo a 0x0800' 7C84.
2. Vuelva a escribir la dirección señalada por R1 (el valor en 0x2000'27D4) al valor del registro R3: 0, luego agregue 4 al valor en R1 y actualícelo a 0x2000'27D8.
3. R2 disminuye en 4.
4. Compruebe si R2 es 0, si no, salte a la segunda ejecución. Si no, continúe con el siguiente paso.
¡Esto es simplemente un ciclo! ——Bucle en lenguaje C para (r2=0x2F4;r2-=4;r!=0){...}, veamos qué se hace en el bucle.
La primera instrucción carga una dirección en R1 - 0x2000'27D4
Es una dirección RAM Usando esto como punto de partida, en el bucle, la longitud del espacio RAM es. 2F4.Se realizó una operación de compensación. Entonces, ¿por qué IAR hace esto? ¿Eliminar algún registro? Utilice Jlink
para ver esta área de memoria y podrá encontrar que esta área es donde se encuentran las variables globales que definimos. En otras palabras, IAR borrará automáticamente las variables globales que definimos en 0 después de cada reinicio del sistema.
Una vez completada la limpieza, la siguiente instrucción "LDR R2, [R0],
#0x4" establecerá la dirección señalada por R0 - el valor en 0x0800'7C84 - 0 Cargue en el registro R2, luego el valor en R0 se incrementa en 4 y se actualiza a 0x0800'7C88. Luego verifique si
R2 es 0. Aquí R2 es 0. Ejecute 'BX
LR' y regrese a la función __iar_data_init2. Si no es 0, podemos encontrar que lo será. saltar a Se realiza una operación de borrado de ciclo en "4 instrucciones".
Después de leer esto, deberíamos poder adivinar la intención de IAR: __iar_data_init2 comenzó a cargar 0x0800'7C78 a R0, 0x0800'7C9C a R4, [R0,R4] es un área de código de inicio, en la cual Se guardan todas las direcciones y la información que se va a "procesar": las direcciones o parámetros de la función ejecutada. De hecho, esta área también tiene un nombre, llamado: Región$$Tabla$$Base. En esta área, el programa usa R0 como índice y R4 como límite superior. Cuando R0 = R4, se ejecuta __iar_data_init2 y salta a la función main().
Bien, mantenemos nuestra suposición y continuamos rastreando el puntero de nuestra PC: estamos de vuelta en la función __iar_data_init2. Lo primero es comparar los valores de R0 y R4. Desafortunadamente, sí, todavía no. igual, y fuimos llevados a 0x0800'7D5C. En este punto, deberíamos poder ver que este es un "bucle principal" de __iar_data_init2, que también verifica nuestra intención para IAR Guess~
"Main. bucle" en __iar_data_init2:
08007D5C F8501B04 LDR R1, [R0], #0x4
08007D60 4788 BLX R1
08007D62 42A0 CMP R0, R4
Podemos escribir de manera equivalente como: for(r0=0x0800'7C78,r4=0x0800'7C9C;r0!=r4;r=4){.. .}
En este momento, nuestro R0 es 0x0800'7C88 Después de la "Instrucción 1", R0 se convierte en 0x0800'7C8C y R1 es 0x0800'7C55. Echemos un vistazo a las operaciones que realizará IAR en 7C55.
__iar_copy_init2:
08007C54 B418 EMPUJAR {R3,R4}
08007C56 E009 B 0x8007C6C
08007C58 F8501B04 LDR R1, [R0] , #0x4
08007C5C F8502B04 LDR R2, [R0], #0x4
08007C60 F8514B04 LDR R4, [R1], #0x4
08007C64 F8424B04 STR R4 , [R2], #0x4
08007C68 1F1B SUBS R3, R3, #0x4
08007C6A D1F9 BNE 0x8007C60
08007C6C F8503B04 LDR R3, [R0], #0x4
08007C70 2B00 CMP R3, #0x0
08007C72 D1F1 BNE 0x8007C58
08007C74 BC12 POP {R1,R4}
08007C76 4770 BX LR
Esta es una función llamada __iar_copy_init2. ¿Qué operación de "copia" realiza?
Primero inserte R3 y R4 en la pila, luego salte a 0x0800'7C6C, tome el parámetro 0x238 de R0——Región$$Table$$Base y colóquelo en R3, continúe
Todos deberían estar familiarizados con el comando. 0x238 no es 0, por lo que nos llevaron a 7C58 Nuevamente, sacamos el parámetro 0x0800'7F14 de Region$$Table$$Base y lo colocamos en R1. Table$$Base Saque el parámetro 0x2000'2AC8 y póngalo en R2. Los espectadores atentos deberían poder notar que esto es casi lo mismo que obtener los parámetros en __iar_zero_init2: primero saque el tamaño y luego saque la dirección; solo que hay una dirección más aquí, sí, esto es "copiar", la siguiente instrucciones p>
08007C60 F8514B04 LDR R4, [R1], #0x4
08007C64 F8424B04 STR R4, [R2], #0x4
08007C68 1F1B SUBS R3, R3 , #0x4
08007C6A D1F9 BNE 0x8007C60
Es otra "4 instrucción". La instrucción 1 lee los datos apuntados a la dirección R1 a R4, y la instrucción 2 reescribe los datos apuntados. los datos de la dirección R2 a R4, las instrucciones 3 y 4 completan un ciclo.
Todos deberían entender en este punto: se trata de una operación de "copia", que comienza desde la dirección Flash 0x0800'7F14 y copia datos de longitud 0x238 a la dirección RAM 0x2000'2AC8.
A través de Jlink podemos ver que esta área es una variable global que hemos definido e inicializado. En otras palabras, después de cada reinicio, IAR inicializa aquí las variables globales.
Después de ejecutar estas "4 instrucciones", los parámetros se extraen nuevamente de Region$$Table$$Base, que es 0. Después de la comparación, se cumplen las condiciones y la función devuelve __iar_data_init2.
En este momento, R0 ya es 0x0800'7C9C y R4 son iguales, y __iar_data_init2 finalmente completó su misión.
08007D98 2000 MOVS R0, #0x0
08007D9A F7FDFC49 BL main
Después de borrar R0, IAR abandona la iniciativa y entrega el puntero de la PC al usuario. punto de entrada del programa - main().
Pero tenga en cuenta que la instrucción BL se usa aquí para saltar a principal. En otras palabras, la función principal es solo una subrutina en manos de IAR. Si la función principal llega al final, salga, etc. Se ejecutará a continuación. La función "salir" proporcionada por IAR. Estas funciones se descompondrán la próxima vez~
En resumen, antes de iniciar la función main(), IAR ejecutó Reset_Handler y llamó a
SystemInit() (proporcionado por la biblioteca ST) para el reloj. , Flash Lea la inicialización y transfiérala a __iar_program_start para su ejecución
__low_level_init y __iar_data_init2, y en __iar_data_init2, llame a __iar_zero_init2 y __iar_copy_init2 sucesivamente para variables globales y variables inicializadas globales Realice las operaciones de inicialización correspondientes. Finalmente, llame a la función main() para ejecutarla.
Esto es lo que hace IAR antes de iniciar la función main(). No es tan misterioso. Siempre que dediques algo de tiempo, puedes rastrear y analizar este proceso.
blogs.com/mssql/archive/2011/01/29/tt146.html