IAR Embedded Workbench for AVR 으로 안전 프로그래밍
기술노트 59350
아키텍처:
AVR
컴포넌트:
compiler
업데이트:
2021-05-31 오전 7:18
소개
본 기술 노트는 IAR Embedded Workbench for AVR로 안전하게 프로그래밍 하는 방법에 대해서 기술합니다.
동시에 접근하는 변수를 보호하라
여러 스레드에서 액세스하는 변수는 적절하게 표시해야 하며 적절한 보호를 받아야 합니다. 유일한 예외는 항상 읽기 전용 변수입니다.
변수를 올바르게 표시하려면 volatile 키워드를 사용하십시오. 이것은 컴파일러에게 변수를 다른 스레드로부터 변경할 수 있다는 것을 알려준다. 그런 다음 컴파일러는 변수에 대한 최적화(예: 레지스터에서 변수의 추적 유지)를 피하고, 쓰기를 지연시키지 않으며, 소스 코드에 주어진 횟수만큼만 변수에 액세스하도록 주의해야 한다.
변수에 액세스하는 시퀀스는 중단되어서는 안 되며, 이는 인터럽트 가능 코드의 __monitor 키워드를 사용하여 수행할 수 있습니다. 쓰기 및 읽기 시퀀스 모두에 대해 이 작업을 수행해야 합니다. 그렇지 않으면 부분적으로 업데이트된 변수를 읽을 수 있습니다.
이는 모든 크기의 모든 변수에 적용됩니다. 바이트 크기의 변수에 액세스하는 것은 세부적인 작업이 될 수 있지만 이는 보장되지 않으며 컴파일러 출력을 항상 연구하지 않는 한 이 변수에 의존해서는 안 됩니다. __monitor 키워드를 사용하여 시퀀스가 세부적인 작업인지 확인하는 것이 더 안전합니다.
가능한 인라인 어셈블러보단, 내장 함수를 사용
AVR 장치에는 정확한 타이밍과 특수 지침 시퀀스가 필요한 메커니즘이 있습니다. 이러한 메커니즘은 일반적으로 고유 함수의 형태로 이용 가능하다. 사용 가능한 고유 함수는 inc\inavr.h 헤더 파일에 선언된다.
내장 함수는 정규 함수 호출처럼 보이지만, 컴파일러가 인식하는 함수이다. 소스 파일에서 함수 호출처럼 보이지만, 그 효과는 보통 컴파일러가 일련의 인라인 명령어들을 생성한다는 것이다.
인라인 어셈블러를 사용하는 것보다 본질적인 함수의 장점은 컴파일러가 현재 일어나고 있는 것을 이해하고 시퀀스를 레지스터 할당 및 변수와 적절하게 인터페이스할 수 있다는 것이다. 컴파일러는 또한 그러한 시퀀스를 최적화하는 방법을 알 수 있는데, 이는 컴파일러는 인라인 어셈블리러 시퀀스로 수행할 수 없는 것이다. 마지막 효과는 원하는 시퀀스가 코드에 적절하게 통합되고 컴파일러가 코드를 완벽하게 최적화하도록 하는 것입니다.
가능한 인라인 어셈블러보단, 어셈블러 함수사용
인라인 어셈블러 시퀀스는 C 코드에서 생성된 주변 코드에 대한 인터페이스가 잘 정의되지 않았습니다. 이는 인라인 어셈블리러 코드를 취약하게 만들고 나중에 컴파일러를 업그레이드하면 유지 보수 문제가 될 수 있습니다. 컴파일러는 또한 인라인 어셈블러 코드를 덜 취약하게 만들기 위해 인라인 어셈블러로 함수를 최적화하는 것을 피한다.
따라서 인라인 어셈블러는 피하는 것이 가장 좋습니다. 많은 경우, 특수 어셈블러 코드(위 참조)를 작성할 필요성을 줄이는 내장 함수가 있다. 대신 그것들을 사용한다.
적절한 내장 함수가 없는 경우, 종종 C에서 호출된 어셈블러 모듈에 어셈블러 코드를 넣는 것이 최선이다. 다음과 같은 몇 가지 이점이 있다:
- 함수 호출 메커니즘은 잘 정의되어 있으며 향후 컴파일러 릴리스에서는 변경되지 않을 것이다. __version_1 메커니즘은 일반적으로 사용하는 것이 가장 좋습니다. 이렇게 하면 취약한 코드 문제가 제거됩니다.
- 코드의 가독성이 높아질 것 입니다.
- 최적화기(optimizer)가 작동할 수 있도록 합니다.
그러나 함수 호출이 오버헤드를 많이 추가하지 않습니까? 그것은 다릅니다. 물론, CALL과 RET 명령의 형태로 약간의 오버헤드가 있고 컴파일러는 일부 레지스터를 스크래치(함수 호출에 의해 파괴됨)로 간주합니다.
한편, 컴파일러는 또한 모든 스크래치 레지스터가 인라인 어셈블러 명령에 의해 파괴된다고 가정할 것입니다. 이렇게 하면 CALL과 RET의 오버헤드가 남습니다. 대부분의 경우 최적화 도구를 끄는 오버헤드는 CALL 및 RET 명령의 오버헤드를 쉽게 능가합니다.
eeprom 쓰기 메커니즘 보호
eeprom 변수에 쓰는 메커니즘은 재진입되지 않으므로 보호되어야 합니다. 이것은 만약 당신이 두 스레드로부터 eeprom을 쓴다면(예를 들어, 메인 코드와 인터럽트), 당신은 eeprom 쓰기 메커니즘을 사용하는 어떤 코드에 의해서도 시퀀스가 중단되지 않도록 하기 위해 eeprom에 쓰기를 보호하기 위해 __monitor 키워드를 사용할 필요가 있을 것입니다.
word SFR 쓰기 보호
Word SFR은 AVR 내의 임시 바이트를 사용하여 액세스합니다. 이러한 임시 바이트는 하나만 있습니다. 두 개의 스레드(예: 메인 코드와 인터럽트)에서 word SFR에 액세스하는 경우 인터럽트로부터 word 액세스를 보호해야 하며, 그렇지 않으면 자주 발생하지 않는 매우 미묘한 버그가 발생합니다. 만약 당신이 인터럽트 내부의 인터럽트를 켜는 습관이 있다면, 당신은 또한 그러한 인터럽트에서의 word SFR 접근을 보호해야 할 것입니다.
잠긴 레지스터에 push 금지
레지스터를 잠그고 해당 레지스터에 글로벌 변수를 넣는 경우 해당 레지스터를 응용 프로그램에서 이 목적으로 잠근 것으로 간주합니다. 이 레지스터를 자신의 어셈블러 코드에 사용하지 말고 레지스터를 빌려주는 동안 스택에 있는 변수를 push 하면 됩니다.
그 이유는 인터럽트에서 잠긴 레지스터 변수에 액세스할 가능성이 높기 때문입니다. 속도를 위해 변수를 레지스터에 저장하면 인터럽트를 통해 액세스할 수 있습니다. 속도를 원하는 곳이 여기이기 때문입니다.
또한 타사 어셈블러 코드를 가져올 경우 레지스터를 사용할 수 있으며 스택에 보존하면 인터럽트가 발생할 때 도움이 되지 않습니다.
결론은, 항상 잠긴 레지스터는 건들지 않는 것으로 생각하시기 바랍니다!
모든 제품 이름은 해당 소유자의 상표 또는 등록 상표입니다.