Witam
Zrobiłem funkcję do testowania ramu dla Atmega16 AVR 8-bitowiec. Oto jej listing :
#define RAM_END (0x045F)
#define RAM_START (0x0060)
/* function check practical RAM without CPU register space and without SFR register space */
uint8_t ram_test()
{
volatile register uint8_t failure;
volatile register uint8_t SaveRamData;
volatile register uint8_t *pRamAddr;
failure = FALSE;
/* RAM addresses: 0x60 to 0x41F : 1KB = 1024 bytes */
for ( pRamAddr = (uint8_t *)RAM_START; pRamAddr <= (uint8_t *)RAM_END; pRamAddr++ )
{
/* save RAM data cell to the register */
SaveRamData = *pRamAddr;
/* write test pattern 0xAA to RAM */
*pRamAddr = RAM_TESTPATTERN_AA;
/* read test pattern 0xAA back and check - if pattern is failed , set return value as fail */
if (*pRamAddr != RAM_TESTPATTERN_AA) failure = TRUE;
/* write test pattern 0x55 to RAM */
*pRamAddr = RAM_TESTPATTERN_55;
/* read test pattern 0x55 back and check - if pattern is failed , set return value as fail */
if (*pRamAddr != RAM_TESTPATTERN_55) failure = TRUE;
/* restore data back to RAM */
*pRamAddr = SaveRamData;
}
/* return failure register */
return failure;
}
Przez kompilator zostaje to przetworzone na assemblera w takie coś :
000002ba <ram_test>:
2ba: df 93 push r29 R29 na stos
2bc: cf 93 push r28 R28 na stos
2be: 00 d0 rcall .+0 ; 0x2c0 <ram_test+0x6>
2c0: cd b7 in r28, 0x3d ; 61 stack pointer 1-st byte do R28
2c2: de b7 in r29, 0x3e ; 62 stack pointer 2-nd byte do R29
2c4: 19 82 std Y+1, r1 ; 0x01 R1 pod adres (stack pointer + 1) - średnio rozumiem tą instrukcję ???
2c6: e0 e6 ldi r30, 0x60 ; 96 załaduj do R30 adres początku ramu 1-st byte
2c8: f0 e0 ldi r31, 0x00 ; 0 załaduj do R31 adres początku ramu 2-nd byte
2ca: 3a ea ldi r19, 0xAA ; 170 ładuj wzorzec 0xAA do R19
2cc: 91 e0 ldi r25, 0x01 ; 1 ładuj wartość 1 do R25
2ce: 25 e5 ldi r18, 0x55 ; 85 ładuj wzorzec 0x55 do R18
2d0: 80 81 ld r24, Z zrzuć wartość badanej komórki ram do rejestru R24
2d2: 8a 83 std Y+2, r24 ; 0x02 wrzuć rejestr R24 pod adres (stack pointer + 2) - ???
2d4: 30 83 st Z, r19 zapis badanej komórki ram patternem 0xAA
2d6: 80 81 ld r24, Z odczytaj badaną komórkę ram i wrzuć do rejestru R24
2d8: 8a 3a cpi r24, 0xAA ; 170 sprawdź czy w R24 mamy 0xAA
2da: 09 f0 breq .+2 ; 0x2de <ram_test+0x24>jeśli tak to przeskocz kolejną instrukcję
2dc: 99 83 std Y+1, r25 ; 0x01 wrzuć R25 (czyli TRUE) na stos pod adres (stack pointer + 1)
2de: 20 83 st Z, r18 zapis badanej komórki ram patternem 0x55
2e0: 80 81 ld r24, Z odczyt badanej komórki ram do R24
2e2: 85 35 cpi r24, 0x55 ; 85 sprawdź czy w R24 mamy 0x55
2e4: 09 f0 breq .+2 ; 0x2e8 <ram_test+0x2e>jeśli tak to przeskocz następną instrukcję
2e6: 99 83 std Y+1, r25 ; 0x01 wrzuć R25 (czyli TRUE) na stos pod adres (stack pointer + 1)
2e8: 8a 81 ldd r24, Y+2 ; 0x02 wyciągnij daną ze stosu (stack pointer + 2) - powinna być tam oryginalna zawartość
2ea: 81 93 st Z+, r24 przywrócenie wartości komórce ram oraz inkrementacja adr.-pokazuje teraz kolejną
2ec: 84 e0 ldi r24, 0x04 ; 4 ładuj 4 pod R24
2ee: e0 36 cpi r30, 0x60 ; 96 sprawdź czy adres doszedł do końca (z przeniesieniem)
2f0: f8 07 cpc r31, r24 sprawdź czy adres doszedł do końca
2f2: 71 f7 brne .-36 ; 0x2d0 <ram_test+0x16>jeśli nie to rób kolejną iterację (skok do 0x2d0)
2f4: 89 81 ldd r24, Y+1 ; 0x01
2f6: 0f 90 pop r0
2f8: 0f 90 pop r0
2fa: cf 91 pop r28
2fc: df 91 pop r29
2fe: 08 95 ret
Dokonałem gruntownej analizy tego podprogramu i mam pytania:
- Czy instrukcja
2c4: 19 82 std Y+1, r1 ; 0x01
wkłada na stos rejestr R1 ?
2. Jaki jest tego sens jeśli ani nie jest ten rejestr R1 przywracany oraz to miejsce na stosie jest w dalszej części nadpisywane rejestrem R25 ?
3. Dlaczego na początku mamy dwa razy push a na końcu jest aż cztery razy pop jeśli przecież zwracaną wartością jest uint8_t a procek to Atmega16 czyli ośmiobitowiec ? Jeśli żadna instrukcja poza push i pop nie modyfikuje stack pointera to to bezsensu wg mnie!
4. Czy instrukcje operujące na stosie pośrednio (czyli std Y+1, ...) modyfikują stack pointera ?
5. Zakładając że Y przechowuje aktualną wartość stack pointera to czy instrukcja std Y+1, ... pisze od tego miejsca o jeden adres w górę czy w dół, bo składnia wskazuje że w górę a przecież stos powinien rozrastać się w dół adresów ?
6. Z kodu asm wynika że rejestr R24 będzie zmieniony po zawołaniu tego podprogramu, czy to normalne ?
7. Dlaczego nie są spełnione założenia kompilatora gcc ABI (aplication binary interface) które powinny gwarantować że w ciele rozwinięcia asm R1 będzie zerem ?
8. Czy mechanizm wykryje również uszkodzenie komórki o adresie stack pointer - 2 i stack pointer -1 ?
Ogółem mechanizm działa tak że iteruje od adresu początkowego ramu do adresu końca ramu, jednak stos jest umieszczony właśnie na końcu ramu i rozrasta się w kierunku mniejszych adresów (sprawdzałem to niedawno dokładnie i jestem tego pewien).
Funkcja ta testuje ram zapisując każdą komórkę wzorcem 0xAA odczytując i sprawdzając czy się wzorzec poprawnie zapisał.
Następnie to samo ze wzorcem 0x55. Zwracana wartość którą otrzymuję to false (czyli ram sprawny).
Jednak nie rozumiem jak to ustrojstwo może w ogóle działać jeśli przecież z kodu assemblera wynika że zanim dojdzie do sprawdzenia to dana komórka jest odkładana na stos. Weźmy bowiem pod uwagę moment gdy program sprawdza komórkę stosu na którą jednocześnie chce zrzucać coś. Zrobi tak: zrzuci ją na stos, zapisze wzorcem, posprawdza se a następnie będzie chciał odtworzyć a przecież oryginalna zawartość się nadpisała zatem nie potrafi odtworzyć tej komórki stosu. Nie rozumiem jak to może działać. Bardzo pomocnym byłoby gdyby ktoś po prostu zechciał odpowiedzieć na powyższe ponumerowane pytania.
Priorytetem jest dla mnie pytanie 3, potem 2, następnie 5 potem 6 a na końcu 7. Reszta może poczekać.
Oczywiście mam po swojemu odpowiedzi ale gdyby ktoś był łaskaw to sprawdzić to sobie swoje zweryfikuję i wyciągnę wnioski raz na zawsze!
Obiecuję że w przyszłości nie będę tak zanudzać!
Gorąco pozdrawiam i z góry dziękuję za zainteresowanie.
Adam