He desarrollado inspirado desde aquí un código de inicio de metal desnudo para arm cortex M3. Sin embargo, me encuentro con el siguiente problema: supongamos que declaro una variable global no inicializada, digamos de tipo unsigned char en main.c
#include ...
unsigned char var;
...
int main()
{
...
}
esto hace que la región .bss en STM32 f103 comience en _BSS_START = 0x20000000 y termine en _BSS_END = 0x20000001. Ahora, el código de inicio
unsigned int * bss_start_p = &_BSS_START;
unsigned int * bss_end_p = &_BSS_END;
while(bss_start_p != bss_end_p)
{
*bss_start_p = 0;
bss_start_p++;
}
intenta inicializar a cero toda la región .bss. Sin embargo, dentro de ese ciclo while, el puntero aumenta con 4 bytes, por lo tanto, después de un paso bss_start_p = 0x20000004, por lo tanto, siempre será diferente de bss_end_p, lo que conduce a un ciclo infinito, etc.
¿Hay alguna solución estándar para esto? ¿Se supone que debo "forzar" de alguna manera la dimensión de la región .bss para que sea un múltiplo de 4? ¿O debería usar un puntero a un personaje sin signo para recorrer la región .bss? Quizás algo como:
unsigned char * bss_start_p = (unsigned char *)(&_BSS_START);
unsigned char * bss_end_p = (unsigned char *)(&_BSS_END);
while(bss_start_p != bss_end_p)
{
*bss_start_p = 0;
bss_start_p++;
}
```
Respuestas:
Como sospecha, esto está sucediendo porque el tipo de datos int sin signo tiene un tamaño de 4 bytes. Cada
*bss_start_p = 0;
declaración en realidad borra cuatro bytes del área bss.El rango de memoria bss debe alinearse correctamente. Simplemente puede definir _BSS_START y _BSS_END para que el tamaño total sea un múltiplo de cuatro, pero esto generalmente se maneja permitiendo que el script del vinculador defina las ubicaciones de inicio y detención.
Como ejemplo, aquí está la sección de enlaces en uno de mis proyectos:
Las
ALIGN(4)
declaraciones se encargan de las cosas.Además, es posible que desee cambiar
while(bss_start_p != bss_end_p)
a
while(bss_start_p < bss_end_p)
.Esto no evitará el problema (ya que puede estar borrando 1-3 bytes más de lo que desea), pero podría minimizar el impacto :)
fuente
while(bss_start_p < bss_end_p - 1)
seguido de un borrado en bytes del rango de memoria restante eliminaría la última preocupación.La solución estándar es
memset()
:Si no puede usar la biblioteca estándar, tendrá que decidir si está bien en su caso redondear el tamaño del área de memoria hasta 4 bytes y continuar usando
unsigned int *
; o si necesita ser estricto al respecto, en cuyo caso deberá usarlounsigned char *
.Si redondeas el tamaño, como en tu primer ciclo, entonces
bss_start_p
puede terminar siendo mayor quebss_end_p
eso, pero es fácil lidiar con una comparación menor que<
en una prueba de desigualdad.Por supuesto, también podría llenar la mayor parte del área de memoria con transferencias de 32 bits, y solo los últimos bytes con transferencias de 8 bits, pero eso es más trabajo por poca ganancia, particularmente aquí cuando es solo una pieza de código de inicio.
fuente
memset()
. Pero la alineación a 4 bytes es más o menos imprescindible. Entonces por qué no hacerlo?memset()
, y C es en lo que parecen estar programando. La implementación simple dememset()
es también solo ese bucle, no es que dependa de mucho más. Como se trata de un microcontrolador, también supongo que no hay un enlace dinámico o algo así (y mirando el enlace, no lo hay, es solo una llamadamain()
después de ese ciclo de puesta a cero), por lo que el compilador debería ser capaz de caermemset()
allí junto con otras funciones (o para implementarlo en línea).Solo cambia
!=
a<
. Por lo general, ese es un mejor enfoque, ya que trata problemas como este.fuente
Hay innumerables otros sitios y ejemplos. Muchos miles, si no decenas de miles. Existen las conocidas bibliotecas c con scripts de enlace y código boostrap, newlib, glibc en particular, pero hay otros que puede encontrar. Bootstraping C con C no tiene sentido.
Su pregunta ha sido respondida, está tratando de hacer una comparación exacta de cosas que podrían no ser exactas, que podrían no comenzar en un límite conocido o terminar en un límite conocido. Por lo tanto, puede hacer menos que nada, pero si el código no funcionó con una comparación exacta, eso significa que está pasando a cero .bss en la siguiente sección, lo que puede o no causar que sucedan cosas malas, por lo que simplemente reemplace con un valor menor que no la solución.
Así que aquí va TL; DR está bien. No arranca un idioma con ese idioma, puede salirse con la suya, pero está jugando con fuego cuando lo hace. Si solo está aprendiendo cómo hacer esto, debe ser cauteloso, no tener suerte o hechos que aún no ha descubierto.
El script de enlazador y el código de arranque tienen una relación muy íntima, están casados, unidos en la cadera, no se desarrolla uno sin el otro que conduce a un fracaso masivo. Y desafortunadamente, el script del enlazador está definido por el enlazador y el lenguaje ensamblador definido por el ensamblador, por lo que a medida que cambia las cadenas de herramientas, tendrá que volver a escribir ambos. ¿Por qué lenguaje ensamblador? No necesita bootstrap, los lenguajes compilados generalmente sí. C lo hace si no desea limitar su uso del lenguaje, comenzaré con algo muy simple que tenga requisitos específicos mínimos de cadena de herramientas, no asuma que las variables .bss son cero (hace que el código sea menos legible si la variable nunca se inicializa en ese idioma) , trate de evitar esto, no es cierto para las variables locales, por lo que debe tener en cuenta cuándo usarlo. Entonces, ¿por qué estamos hablando de .bss y .data ??? (los globales son buenos para este nivel de trabajo, pero ese es otro tema)) la otra regla para la solución simple es no inicializar variables en la declaración, hágalo en el código. sí quema más flash, generalmente tiene mucho, no todas las variables se inicializan con constantes de todos modos que terminan consumiendo instrucciones.
Se puede deducir por el diseño de la cortezax-m que pueden haber estado pensando que no hay ningún código de arranque en absoluto, por lo que no hay soporte .data ni .bss. La mayoría de las personas que usan globals no pueden vivir sin ellas, así que aquí va:
Podría hacer esto más minimalista, pero un ejemplo funcional mínimo para todos los córtex-ms que usan la cadena de herramientas gnu, no recuerdo qué versiones puede comenzar con 5.xx más o menos a través del 9.xx actual Cambié los scripts del enlazador en algún lugar alrededor de 3. xx o 4.xx cuando aprendí más y cuando gnu cambió algo que rompió el primero.
oreja:
punto de entrada en el código C:
script de enlazador.
Todos estos podrían ser más pequeños y seguir funcionando, se agregaron algunas cosas adicionales aquí solo para verlo en el trabajo.
construcción y enlace optimizados.
para algunos proveedores, desea utilizar 0x08000000 o 0x01000000 u otras direcciones similares, ya que el flash se asigna allí y se refleja en 0x00000000 en algunos modos de arranque. algunos solo tienen una gran parte del flash reflejado en 0x00000000, por lo que desea que el punto de la tabla de vectores en el espacio del flash de la aplicación no sea cero. ya que está basado en una tabla de vectores, todo funciona.
Primero, tenga en cuenta que los córtex-ms son máquinas de solo pulgar y, por cualquier motivo, imponen una dirección de función de pulgar, lo que significa que lsbit es impar. Conozca sus herramientas, las directivas .thumb_func le dicen al ensamblador gnu que la siguiente etiqueta es una dirección de función de pulgar. hacer lo +1 en la tabla conducirá al fracaso, no caigas en la tentación de hacerlo, hazlo bien. Hay otras formas de ensamblador de GNU para declarar una función. Este es el enfoque mínimo.
no arrancará si no obtienes la tabla de vectores correcta.
podría decirse que solo necesita el vector del puntero de la pila (puede poner cualquier cosa allí si desea establecer el puntero de la pila en el código) y el vector de reinicio. Puse cuatro aquí sin ninguna razón en particular. Por lo general, pon 16 pero quería acortar este ejemplo.
Entonces, ¿qué es lo mínimo que debe hacer un bootstrap C? 1. establezca el puntero de la pila 2. cero .bss 3. copie .data 4. bifurque o llame al punto de entrada C
el punto de entrada C generalmente se llama main (). pero algunas cadenas de herramientas ven main () y agregan basura adicional a su código. Intencionalmente uso un nombre diferente. YMMV.
la copia de .data no es necesaria si todo esto está basado en ram. Al ser un microcontrolador Cortex-M, es técnicamente posible pero poco probable, por lo que se necesita la copia .data ..... si hay .data.
Mi primer ejemplo y un estilo de codificación es no confiar en .data ni .bss, como en este ejemplo. Arm se encargó del puntero de la pila, por lo que lo único que queda es llamar al punto de entrada. Me gusta tenerlo para que el punto de entrada pueda regresar, mucha gente argumenta que nunca debes hacer eso. entonces podrías hacer esto:
y no volver de centry () y no tener código de reinicio del controlador.
el enlazador ha puesto las cosas donde pedimos. Y en general tenemos un programa completamente funcional.
Así que primero trabaje en el script del enlazador:
enfatizando que los nombres rom y ram no tienen significado, solo conectan los puntos para el enlazador entre secciones.
agregue algunos elementos para que podamos ver lo que hicieron las herramientas
agregue algunos elementos para colocar en esas secciones. y obten
aquí están las cosas que estamos buscando en ese experimento (no hay razón para cargar o ejecutar ningún código ... conozca sus herramientas, aprenda)
Entonces, lo que aprendimos aquí es que la posición de las variables es muy sensible en los scripts de gnu linker. tenga en cuenta la posición de data_rom_start vs data_start, pero ¿por qué funciona data_end ? Te dejaré resolver eso. Ya entendiendo por qué uno no querría tener que meterse con los scripts del enlazador y simplemente llegar a una programación simple ...
así que otra cosa que aprendimos aquí es que el enlazador alineó data_rom_start para nosotros, no necesitábamos un ALIGN (4) allí. ¿Debemos suponer que eso siempre funcionará?
También tenga en cuenta que se completó en el camino hacia afuera, tenemos 5 bytes de .data pero se completó a 8. Sin ALIGN () s, ya podemos hacer la copia usando palabras. Según lo que vemos hoy con esta cadena de herramientas en mi computadora, ¿podría ser cierto para el pasado y el futuro? Quién sabe, incluso con ALIGNs necesita verificar periódicamente para confirmar que alguna nueva versión no rompió las cosas, lo harán de vez en cuando.
de ese experimento pasemos a esto solo para estar seguros.
moviendo los extremos hacia adentro para ser consistente con lo que hacen otras personas. Y eso no lo cambió:
Una prueba rápida más:
dando
no es necesario rellenar entre rebote y .align
Ohh, claro, ahora recuerdo por qué no pongo el final dentro. porque NO FUNCIONA.
algún código simple, pero muy portátil para casarse con este script enlazador
dando
podemos parar allí o seguir adelante. Si inicializamos en el mismo orden que el script del enlazador, está bien si pasamos a lo siguiente, ya que aún no hemos llegado allí. y stm / ldm solo son necesarios / deseados para usar direcciones alineadas por palabras, por lo que si cambia a:
con bss primero en el script del enlazador, y sí, no quieres bls.
esos bucles irán más rápido. ahora no sé si los buses ahb pueden tener 64 bits de ancho o no, pero para un brazo de tamaño completo querrás alinear estas cosas en los límites de 64 bits. un ldm / stm de cuatro registros en un límite de 32 bits pero no un límite de 64 bits se convierte en tres transacciones de bus separadas, donde alineado en un límite de 64 bits es una transacción única que ahorra varios relojes por instrucción.
dado que estamos haciendo baremetal y somos totalmente responsables de todo lo que podemos poner, digamos bss primero, luego datos, luego, si tenemos un montón, entonces la pila crece de arriba hacia abajo, por lo que si ponemos a cero bss y derramamos algo siempre que comencemos en el lugar correcto que está bien todavía no estamos usando esa memoria. luego copiamos .data y podemos derramar en el montón que está bien, el montón o no hay mucho espacio para la pila, por lo que no estamos pisando a nadie / nada (siempre y cuando nos aseguremos de que en el script del enlazador lo hagamos). Si existe alguna preocupación, haga que ALIGN () sea más grande para que siempre estemos dentro de nuestro espacio para estos rellenos.
entonces mi solución simple, tómalo o déjalo. bienvenido a corregir cualquier error, no ejecuté esto en hardware ni en mi simulador ...
ponlo todo junto y obtienes:
tenga en cuenta que esto funciona con arm-none-eabi- y arm-linux-gnueabi y las otras variantes, ya que no se utilizó ningún ghee whiz.
Cuando mires a tu alrededor, encontrarás que la gente se volverá loca con cosas geniales de ghee en sus scripts de enlazadores, enormes y monstruosas cosas de fregadero de cocina. Es mejor saber cómo hacerlo (o mejor cómo dominar las herramientas para que pueda controlar lo que sucede) en lugar de confiar en las cosas de otra persona y no saber dónde se romperá porque no comprende y / o no quiere investigar eso.
como regla general, no inicie un lenguaje con el mismo idioma (bootstrap en este sentido, que significa ejecutar código que no compila un compilador con el mismo compilador), desea utilizar un lenguaje más simple con menos bootstrap. Es por eso que C se realiza en el ensamblaje, no tiene requisitos de arranque, solo debe comenzar desde la primera instrucción después del reinicio. JAVA, seguro de que puede escribir el jvm en C y arrancar ese C con asm y luego arrancar el JAVA si lo hace con C pero también ejecutar el JAVA en C también.
Debido a que controlamos los supuestos en estos bucles de copia, son por definición más estrictos y más limpios que memcpy / memset sintonizados a mano.
Tenga en cuenta que su otro problema fue este:
si estos son locales bien, no hay problema, si estos son globales, entonces necesita .data inicializado primero para que funcionen y si intenta ese truco para hacer .data, entonces fallará. Variables locales, bien, eso funcionará. si por alguna razón decidiste hacer los locales estáticos (globales locales que me gusta llamarlos), entonces estás nuevamente en problemas. Cada vez que haces una tarea en una declaración, aunque deberías pensarlo, cómo se implementa y si es seguro / correcto. Cada vez que asume que una variable es cero cuando no se declara, mismo trato, si una variable local no se supone que es cero, si es global, entonces lo es. Si nunca asumes que son cero, entonces nunca tienes que preocuparte.
fuente