A partir daqui, desenvolvi um código de inicialização bare metal para o córtex do braço M3. No entanto, encontro o seguinte problema: suponha que declare uma variável global não inicializada, digamos do tipo char não assinado em main.c
#include ...
unsigned char var;
...
int main()
{
...
}
isso torna a região .bss no STM32 f103 começando em _BSS_START = 0x20000000 e terminando em _BSS_END = 0x20000001. Agora, o código de inicialização
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++;
}
tenta inicializar para zerar toda a região .bss. No entanto, dentro desse loop while, o ponteiro aumenta com 4 bytes, portanto, após uma etapa bss_start_p = 0x20000004, portanto, sempre será diferente de bss_end_p, o que leva a um loop infinito etc.
Existe alguma solução padrão para isso? Devo "forçar" de alguma forma a dimensão da região .bss para ser um múltiplo de 4? Ou devo usar um ponteiro para char não assinado para percorrer a região .bss? Talvez 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++;
}
```
Respostas:
Como você suspeita, isso está acontecendo porque o tipo de dados int não assinado tem 4 bytes de tamanho. Cada
*bss_start_p = 0;
instrução, na verdade, limpa quatro bytes da área bss.O intervalo de memória bss precisa estar alinhado corretamente. Você pode simplesmente definir _BSS_START e _BSS_END para que o tamanho total seja múltiplo de quatro, mas isso geralmente é tratado ao permitir que o script do vinculador defina os locais de início e parada.
Como exemplo, aqui está a seção vinculador em um dos meus projetos:
As
ALIGN(4)
declarações cuidam das coisas.Além disso, você pode querer mudar
while(bss_start_p != bss_end_p)
para
while(bss_start_p < bss_end_p)
.Isso não impedirá o problema (já que você pode limpar de 1 a 3 bytes a mais do que deseja), mas poderá minimizar o impacto :)
fonte
while(bss_start_p < bss_end_p - 1)
seguido de uma limpeza em bytes do intervalo de memória restante eliminaria a última preocupação.A solução padrão é
memset()
:Se você não puder usar a biblioteca padrão, terá que decidir se está correto no seu caso arredondar o tamanho da área de memória até 4 bytes e continuar usando
unsigned int *
; ou se você precisar ser rigoroso, nesse caso, você precisará usarunsigned char *
.Se você arredondar o tamanho, como no seu primeiro loop,
bss_start_p
pode acabar sendo maior que,bss_end_p
mas é fácil lidar com uma comparação menor que a em<
vez de um teste de desigualdade.Obviamente, você também pode preencher a maior parte da área de memória com transferências de 32 bits e apenas os últimos bytes com transferências de 8 bits, mas isso é mais útil para pouco ganho, principalmente aqui quando é apenas um pedaço de código de inicialização.
fonte
memset()
. Mas o alinhamento com 4 bytes é mais ou menos necessário. Então, por que não fazer isso?memset()
e C é o que eles parecem estar programando. A implementação simples dememset()
também é basicamente esse loop, não é como se dependesse de muito mais. Como esse é um microcontrolador, eu também assumo que não há vínculo dinâmico ou algo assim (e olhando para o link, não existe, é apenas uma chamada paramain()
depois desse loop de zeragem), para que o compilador seja capaz de entrarmemset()
nele juntamente com outras funções (ou implementá-lo em linha).Apenas mude
!=
para<
. De qualquer maneira, geralmente é uma abordagem melhor, pois lida com problemas como esse.fonte
Existem inúmeros outros sites e exemplos. Muitos milhares, senão dezenas de milhares. Existem as bibliotecas c conhecidas com scripts de vinculação e código boostrap, newlib, glibc em particular, mas existem outras que você pode encontrar. Bootstraping C com C não faz sentido.
Sua pergunta foi respondida. Você está tentando fazer uma comparação exata de coisas que podem não ser exatas, pode não começar em um limite conhecido ou terminar em um limite conhecido. Portanto, você pode fazer o que é menos, mas se o código não funcionar com uma comparação exata, isso significa que você está zerando o .bss para a próxima seção, que pode ou não fazer com que coisas ruins aconteçam, então substitua-o por um valor menor que não é a solução.
Então aqui vai TL; DR está bem. Você não inicializa um idioma com esse idioma, pode se safar com certeza, mas está brincando com fogo quando faz isso. Se você está apenas aprendendo a fazer isso, precisa estar do lado da cautela, sem ter sorte ou fatos que ainda não descobriu.
O script do vinculador e o código de bootstrap têm um relacionamento muito íntimo, são casados, unidos no quadril, você não desenvolve um sem o outro que leva ao fracasso em massa. E, infelizmente, o script do vinculador é definido pelo vinculador e a linguagem de montagem definida pelo assembler, assim como você altera as cadeias de ferramentas, espera-se que tenha que reescrever os dois. Por que linguagem assembly? Ele não precisa de bootstrap, as linguagens compiladas geralmente precisam. C faz isso se você não limitar seu uso do idioma. Começarei com algo muito simples que possui requisitos mínimos de cadeia de ferramentas, você não assume que as variáveis .bss são zero (torna o código menos legível se a variável nunca for inicializada nesse idioma , tente evitar isso, não é verdade para variáveis locais, portanto, tenha em mente que quando você a usa, as pessoas evitam os globais de qualquer maneira, Então, por que estamos falando sobre .bss e .data ??? (os globais são bons para este nível de trabalho, mas esse é outro tópico)) a outra regra para a solução simples é não inicializar variáveis na declaração, fazê-lo no código. sim queima mais flash, você geralmente tem bastante, nem todas as variáveis são inicializadas com constantes de qualquer maneira que acabam consumindo instruções.
Você pode dizer pelo design do córtex-m que eles podem estar pensando que não há código de autoinicialização, portanto, não há suporte a dados nem .bss. A maioria das pessoas que usam globals não pode viver sem, então aqui vai:
Eu poderia tornar isso mais mínimo, mas um exemplo funcional mínimo para todos os córtex-ms usando a cadeia de ferramentas gnu, não me lembro de quais versões você pode começar com 5.xx ou mais nos atuais 9.xx troquei scripts de vinculador em torno de 3. xx ou 4.xx como eu aprendi mais e como gnu mudou algo que quebrou o meu primeiro.
bootstrap:
ponto de entrada no código C:
script vinculador.
Tudo isso pode ser menor e ainda funcionar, adicionou algumas coisas extras aqui apenas para vê-lo funcionar.
construção e link otimizados.
para alguns fornecedores, você deseja usar 0x08000000 ou 0x01000000 ou outros endereços semelhantes, pois o flash é mapeado lá e espelhado para 0x00000000 em alguns modos de inicialização. alguns têm apenas o flash espelhado em 0x00000000, portanto, você deseja que a tabela do vetor aponte no espaço do flash do aplicativo diferente de zero. uma vez que é baseado em tabela vetorial, tudo funciona.
primeira nota: o córtex-ms são máquinas apenas de polegar e, por qualquer motivo, eles aplicaram um endereço de função de polegar, o que significa que o lsbit é ímpar. Conheça suas ferramentas, as diretivas .thumb_func informam ao assembler do gnu que o próximo rótulo é um endereço de função de polegar. fazer o +1 na tabela levará ao fracasso, não fique tentado a fazê-lo, faça o que é certo. existem outras maneiras de o gnu assembler declarar uma função, essa é a abordagem mínima.
ele não inicializa se você não acertar a tabela de vetores.
indiscutivelmente, você só precisa do vetor do ponteiro da pilha (pode colocar qualquer coisa lá se desejar definir o ponteiro da pilha no código) e do vetor de redefinição. Coloquei quatro aqui sem nenhuma razão específica. Normalmente coloque 16, mas queria encurtar este exemplo.
Então, qual é o mínimo que um bootstrap C precisa fazer? 1. defina o ponteiro da pilha 2. zero .bss 3. copie. Dados 4. ramifique ou chame o ponto de entrada C
o ponto de entrada C é geralmente chamado de main (). mas algumas cadeias de ferramentas vêem main () e adicionam lixo extra ao seu código. Eu intencionalmente uso um nome diferente. YMMV.
a cópia do arquivo .data não será necessária se tudo for baseado em RAM. sendo um microcontrolador córtex-m, é tecnicamente possível, mas improvável, portanto a cópia .data é necessária ..... se houver .data.
Meu primeiro exemplo e um estilo de codificação é não confiar em .data nem em .bss, como neste exemplo. Arm cuidou do ponteiro da pilha, então a única coisa que resta é chamar o ponto de entrada. Eu gosto de tê-lo para que o ponto de entrada possa retornar, muitas pessoas argumentam que você nunca deve fazer isso. você poderia fazer isso então:
e não retornar de centry () e não ter redefinido o código do manipulador.
o vinculador colocou as coisas onde pedimos. No geral, temos um programa totalmente funcional.
Então, primeiro trabalhe no script do vinculador:
enfatizando que os nomes rom e ram não têm significado, eles apenas conectam os pontos do vinculador entre as seções.
adicione alguns itens para que possamos ver o que as ferramentas fizeram
adicione alguns itens para colocar nessas seções. e pegue
aqui estão as coisas que procuramos nessa experiência (observe que não há razão para realmente carregar nem executar qualquer código ... conheça suas ferramentas, aprenda-as)
então o que aprendemos aqui é que a posição das variáveis é muito sensível nos scripts do gnu linker. observe a posição de data_rom_start vs data_start, mas por que data_end funciona? Vou deixar você descobrir isso. Já entendo por que alguém pode não querer mexer nos scripts do vinculador e apenas começar a programação simples ...
outra coisa que aprendemos aqui é que o vinculador alinhado data_rom_start para nós não precisamos de um ALIGN (4) lá. Devemos assumir que isso sempre funcionará?
Observe também que preenchido no caminho para, temos 5 bytes de .data, mas preenchido para 8. Sem ALIGN () s, já podemos fazer a cópia usando palavras. Com base no que vemos com esta cadeia de ferramentas em meu computador hoje, isso pode ser verdade para o passado e o futuro? Quem sabe, mesmo com os ALIGNs, verifique periodicamente para confirmar se alguma nova versão não quebrou as coisas, eles farão isso de tempos em tempos.
a partir desse experimento, vamos passar para isso apenas por segurança.
movendo as extremidades para dentro para ser consistente com o que as outras pessoas fazem. E isso não mudou:
mais um teste rápido:
dando
não há necessidade de alternar entre o ressalto e o alinhamento.
Ohh, certo, agora me lembro porque não coloco o _end__ dentro. porque não funciona.
algum código simples, mas muito portátil, para se casar com esse script vinculador
dando
podemos parar por aí ou continuar. Se inicializarmos na mesma ordem que o script do vinculador, tudo bem se passarmos para a próxima coisa, pois ainda não chegamos lá. e stm / ldm são necessários / desejados apenas para usar endereços alinhados por palavras; portanto, se você mudar para:
com o bss primeiro no script do vinculador, e sim, você não quer bls.
esses loops serão mais rápidos. agora não sei se os barramentos ahb podem ter 64 bits de largura ou não, mas para um braço de tamanho completo, você desejaria alinhar essas coisas nos limites de 64 bits. um registro ldm / stm de quatro em um limite de 32 bits, mas não um limite de 64 bits, torna-se três transações de barramento separadas, onde alinhadas em um limite de 64 bits é uma transação única que salva vários relógios por instrução.
como estamos fazendo baremetal e somos totalmente responsáveis por tudo o que podemos dizer, digamos bss, em seguida, dados e, em seguida, se tivermos um monte de pilha, a pilha cresce de cima para baixo, portanto, se zerarmos bss e derramarmos algumas, desde que iniciemos em No lugar certo, tudo bem, ainda não estamos usando essa memória. copiamos .data e podemos espalhar para o heap, tudo bem, heap ou não, há muito espaço para a pilha, por isso não estamos pisando em ninguém / em nada (desde que tenhamos certeza de que no script do vinculador fazemos isso. se houver uma preocupação, faça com que ALIGN () seja maior, para que possamos sempre estar em nosso espaço para esses preenchimentos.
então minha solução simples, pegue ou largue. bem-vindo a corrigir quaisquer erros, eu não executei isso no hardware nem no meu simulador ...
junte tudo e você obtém:
note que isso funciona com arm-none-eabi- e arm-linux-gnueabi e as outras variantes, já que nenhum material ghee whiz foi usado.
Quando você olha ao redor, você descobre que as pessoas enlouquecem com coisas ghee whiz em seus scripts de vinculação, imensas coisas monstruosas na pia da cozinha. É melhor apenas saber como fazê-lo (ou melhor como dominar as ferramentas para que você possa controlar o que acontece), em vez de confiar nas coisas de outra pessoa e não saber onde isso vai acontecer, porque você não entende e / ou deseja pesquisar isto.
Como regra geral, não inicialize um idioma com o mesmo idioma (nesse sentido, iniciando o código sem compilar um compilador com o mesmo compilador), você deseja usar um idioma mais simples com menos bootstrap. É por isso que C é feito em montagem, não há requisitos de inicialização, basta iniciar a partir da primeira instrução após a redefinição. JAVA, certifique-se de escrever a jvm em C e inicializar esse C com asm e, em seguida, inicializar o JAVA se você desejar com C, mas também executar o JAVA em C também.
Como controlamos as suposições nesses ciclos de cópia, elas são, por definição, mais rígidas e limpas do que o memcpy / memset ajustado manualmente.
Observe que seu outro problema foi este:
se estes são locais, não há problema, se são globais, você precisa primeiro .data inicializado para que funcionem e se você tentar esse truque para executar .data, falhará. Variáveis locais, tudo bem que funcionará. se você, por algum motivo, decidiu criar os locais estáticos (globais locais que eu gosto de chamá-los), então você voltará a ter problemas novamente. Toda vez que você faz uma tarefa em uma declaração, embora deva pensar sobre isso, como isso é implementado e é seguro / são. Toda vez que você assume que uma variável é zero quando não declarada, o mesmo negócio, se uma variável local não for assumida como zero, se for global, será. se você nunca assume que eles são zero, nunca precisa se preocupar.
fonte