1) 컴파일 된 바이너리가 prom / flash에 기록됩니다. USB, 직렬, i2c, jtag 등은 부팅 프로세스를 이해하는 데 관계없이 해당 장치가 지원하는 장치에 따라 장치에 따라 다릅니다.
2) 이것은 일반적으로 마이크로 컨트롤러에게는 해당되지 않으며, 주요 사용 사례는 ROM / 플래시 명령과 램 데이터를 사용하는 것입니다. 어떤 아키텍처이든 상관 없습니다. 비 마이크로 컨트롤러, PC, 랩톱, 서버의 경우 프로그램은 비 휘발성 (디스크)에서 램으로 복사되어 실행됩니다. 일부 마이크로 컨트롤러에서는 정의를 위반 한 것으로 보이지만 하버드를 주장하는 램도 사용할 수 있습니다. 램을 명령어 측에 매핑하는 것을 방해하는 하버드에 대한 것은 없습니다. 전원이 켜진 후 명령어를 얻을 수있는 메커니즘이 필요합니다 (정의를 위반하지만 하버드 시스템은 다른 유용한 기능을 수행해야합니다) 마이크로 컨트롤러보다).
3) 종류.
각 CPU는 설계된대로 결정 론적으로 "부팅"합니다. 가장 일반적인 방법은 전원을 켠 후 첫 번째 명령어의 주소가 재설정 벡터에있는 벡터 테이블입니다.이 주소는 하드웨어가 읽은 다음 해당 주소를 사용하여 실행을 시작하는 주소입니다. 다른 일반적인 방법은 프로세서가 잘 알려진 주소에서 벡터 테이블없이 실행을 시작하도록하는 것입니다. 때때로 칩에는 "스트랩"이 있으며, 리셋을 해제하기 전에 높거나 낮게 묶을 수있는 일부 핀, 로직이 다른 방식으로 부팅하는 데 사용됩니다. CPU 자체, 프로세서 코어를 나머지 시스템과 분리해야합니다. CPU 작동 방식을 이해 한 다음 칩 / 시스템 설계자가 CPU 외부 주변에 주소 디코더를 설정하여 CPU 주소 공간의 일부가 플래시와 통신 할 수 있음을 이해합니다. 일부는 램이 있고 일부는 주변기기 (uart, i2c, spi, gpio 등)가 있습니다. 원하는 경우 동일한 CPU 코어를 사용하여 다르게 감쌀 수 있습니다. 이것은 팔이나 밉을 기반으로 무언가를 구입할 때 얻는 것입니다. 팔과 밉은 CPU 코어를 만듭니다. 칩 코어는 사람들이 자신의 물건을 사서 포장합니다. 여러 가지 이유로 그들은 그 물건을 브랜드에서 브랜드로 호환시키지 못합니다. 그렇기 때문에 핵심 이외의 모든 것에 대해 일반적인 암 질문을 거의 할 수 없습니다.
마이크로 컨트롤러는 칩의 시스템이 되려고 시도하므로 비 휘발성 메모리 (플래시 / 롬), 휘발성 (스램) 및 CPU는 모두 주변 장치와 함께 동일한 칩에 있습니다. 그러나 칩은 내부적으로 플래시가 해당 CPU의 부팅 특성과 일치하는 CPU의 주소 공간에 매핑되도록 설계되었습니다. 예를 들어 CPU에 주소 0xFFFC에 재설정 벡터가있는 경우 유용한 프로그램을 위해 주소 공간에 충분한 플래시 / ROM과 함께 1)을 통해 프로그래밍 할 수있는 해당 주소에 응답하는 플래시 / ROM이 있어야합니다. 칩 설계자는 이러한 요구 사항을 충족시키기 위해 0xF000에서 0x1000 바이트의 플래시를 시작하도록 선택할 수 있습니다. 그리고 아마도 그들은 약간의 램을 더 낮은 주소 또는 0x0000에, 주변 장치를 중간 어딘가에 넣었습니다.
CPU의 다른 아키텍처는 주소 0에서 실행을 시작할 수 있으므로 반대의 작업을 수행하고 플래시를 배치하여 0 근처의 주소 범위에 응답합니다. 예를 들어 0x0000 ~ 0x0FFF라고 말하십시오. 램을 다른 곳에 두십시오.
칩 설계자는 CPU 부팅 방법을 알고 있으며 비 휘발성 스토리지를 플래시 (ROM / ROM)에 배치했습니다. 그런 다음 해당 CPU의 잘 알려진 동작과 일치하도록 부팅 코드를 작성하는 것은 소프트웨어 사용자의 몫입니다. 리셋 벡터 주소는 리셋 벡터에, 부트 코드는 리셋 벡터에 정의한 주소에 배치해야합니다. 툴체인은 여기서 크게 도움이 될 수 있습니다. 때로는 포인트 앤 클릭 아이디어 나 다른 샌드 박스를 사용하여 대부분의 작업을 수행 할 수 있습니다. 고급 언어 (C)로 api를 호출하기 만하면됩니다.
그러나 플래시 / ROM에로드 된 프로그램은 CPU의 유선 부팅 동작과 일치해야합니다. main () 프로그램의 C 부분을 시작하기 전에 main을 시작점으로 사용하려면 몇 가지 작업을 수행해야합니다. AC 프로그래머는 초기 값으로 변수를 선언 할 때 실제로 작동 할 것으로 예상합니다. 글쎄, const 이외의 변수는 램에 있지만 초기 값을 가진 변수가 있으면 초기 값은 비 휘발성 램에 있어야합니다. 따라서 이것은 .data 세그먼트이며 C 부트 스트랩은 .data 항목을 플래시에서 램으로 복사해야합니다 (일반적으로 툴 체인에 의해 결정됩니다). 초기 값없이 선언하는 전역 변수는 프로그램이 시작되기 전에 0으로 가정하지만 실제로는 일부 컴파일러가 초기화되지 않은 변수에 대해 경고하기 시작한다고 가정해서는 안됩니다. 이것은 .bss 세그먼트이며, 램에서 내용 인 0 인 C 부트 스트랩 제로는 비 휘발성 메모리에 저장 될 필요는 없지만 시작 주소와 그 양은 얼마입니까? 다시 한 번 툴체인이 크게 도움이됩니다. 마지막으로 최소한 C 프로그램은 로컬 변수를 가질 수 있고 다른 함수를 호출 할 수 있기 때문에 스택 포인터를 설정해야합니다. 그런 다음 다른 칩 관련 작업이 수행되거나 나머지 칩 관련 작업이 C에서 발생하도록 할 수 있습니다. 비 휘발성 메모리에 저장 될 필요는 없지만 시작 주소와 용량은 얼마입니까? 다시 한 번 툴체인이 크게 도움이됩니다. 마지막으로 최소한 C 프로그램은 로컬 변수를 가질 수 있고 다른 함수를 호출 할 수 있기 때문에 스택 포인터를 설정해야합니다. 그런 다음 다른 칩 관련 작업이 수행되거나 나머지 칩 관련 작업이 C에서 발생하도록 할 수 있습니다. 비 휘발성 메모리에 저장 될 필요는 없지만 시작 주소와 용량은 얼마입니까? 다시 한 번 툴체인이 크게 도움이됩니다. 마지막으로 최소한 C 프로그램은 로컬 변수를 가질 수 있고 다른 함수를 호출 할 수 있기 때문에 스택 포인터를 설정해야합니다. 그런 다음 다른 칩 관련 작업이 수행되거나 나머지 칩 관련 작업이 C에서 발생하도록 할 수 있습니다.
arm의 cortex-m 시리즈 코어 가이 작업을 수행합니다. 스택 포인터는 벡터 테이블에 있으며 재설정 후 코드를 가리 키도록 재설정 벡터가 있으므로 수행해야 할 작업 이외의 작업 벡터 테이블을 생성하기 위해 (어쨌든 보통 asm을 사용합니다) asm없이 순수한 C를 사용할 수 있습니다. 이제는 .data를 복사하거나 .bss를 0으로 만들지 않으므로 cortex-m 기반으로 asm없이 가려고하면 직접해야합니다. 더 큰 특징은 리셋 벡터가 아니라 하드웨어가 권장 C 호출 규칙을 따르고 레지스터를 유지하고 인터럽트 벡터를 올바르게 사용하여 각 핸들러 주위에 올바른 asm을 감쌀 필요가없는 인터럽트 벡터입니다 ( 또는 툴체인이 당신을 위해 그것을 감싸도록 타겟에 대한 툴체인 특정 지시문을 갖도록하십시오).
칩 관련 사항은 예를 들어, 마이크로 컨트롤러는 종종 배터리 기반 시스템에서 사용되므로 전력이 낮으므로 일부 주변 장치를 끈 상태에서 일부는 재설정되지 않으므로 이러한 하위 시스템을 켜야 사용할 수 있습니다. . Uarts, gpios 등 크리스탈이나 내부 발진기에서 곧바로 낮은 클럭 속도가 사용되는 경우가 많습니다. 그리고 시스템 설계에 더 빠른 시계가 필요하다는 것을 보여줄 수 있으므로 초기화하십시오. 시계가 플래시 또는 램에 비해 너무 빠를 수 있으므로 시계를 올리기 전에 대기 상태를 변경해야 할 수도 있습니다. uart 또는 USB 또는 기타 인터페이스를 설정해야 할 수도 있습니다. 그런 다음 응용 프로그램이 그 일을 할 수 있습니다.
컴퓨터 데스크탑, 랩톱, 서버 및 마이크로 컨트롤러는 부팅 / 작동 방식이 다르지 않습니다. 그것들이 대부분 하나의 칩에 있지는 않습니다. BIOS 프로그램은 종종 CPU와 별도의 칩 플래시 / ROM에 있습니다. 최근 x86 CPU가 칩을 지원하는 칩을 동일한 패키지 (pcie 컨트롤러 등)에 점점 더 많이 가져오고 있지만 여전히 램과 롬 오프 칩이 대부분이지만 여전히 시스템이며 여전히 작동합니다. 높은 수준에서 동일합니다. CPU 부팅 프로세스는 잘 알려져 있으며 보드 설계자는 플래시가 부팅되는 주소 공간에 플래시 / ROM을 배치합니다. 해당 프로그램 (x86 pc의 BIOS 부분)은 위에서 언급 한 모든 작업을 수행하고 다양한 주변 장치를 시작하고 dram을 초기화하고 pcie 버스를 열거하는 등의 작업을 수행합니다. 사용자가 BIOS 설정 또는 cmos 설정을 호출 할 때 사용한 기술을 기반으로 사용자가 구성 할 수있는 경우가 많았습니다. 당시 기술이 사용 되었기 때문입니다. 중요하지 않지만, BIOS 부팅 코드의 기능을 변경하는 방법을 알려주기 위해 변경할 수있는 사용자 설정이 있습니다.
다른 사람들은 다른 용어를 사용합니다. 칩 부트, 즉 첫 번째로 실행되는 코드입니다. 때때로 부트 스트랩이라고도합니다. 로더라는 단어가있는 부트 로더는 종종 방해 할 일이 없다면 일반 부팅에서 더 큰 응용 프로그램 또는 운영 체제로 부팅하는 부트 스트랩이라는 것을 의미합니다. 그러나 로더 부분은 부팅 프로세스를 중단 한 다음 다른 테스트 프로그램을로드 할 수 있음을 의미합니다. 예를 들어 임베디드 리눅스 시스템에서 uboot를 사용한 적이 있다면 키를 누르고 일반 부팅을 중지 한 다음 테스트 커널을 램으로 다운로드하여 플래시가 아닌 램으로 부팅하거나 다운로드 할 수 있습니다 자체 프로그램을 사용하거나 새 커널을 다운로드 한 다음 부트 로더가 플래시에 쓰도록하여 다음에 부팅 할 때 새 항목을 실행할 수 있습니다.
CPU 자체까지는 주변 장치의 플래시에서 램을 모르는 코어 프로세서. 부트 로더, 운영 체제, 응용 프로그램에 대한 개념은 없습니다. 실행되는 CPU에 공급되는 일련의 명령입니다. 서로 다른 프로그래밍 작업을 구별하기위한 소프트웨어 용어입니다. 서로 소프트웨어 개념.
일부 마이크로 컨트롤러에는 칩 공급 업체가 제공하지 않는 별도의 부트 로더가 있으며 별도의 플래시 또는 별도의 플래시 영역에서 수정할 수 없습니다. 이 경우 종종 핀 또는 핀 세트 (스트랩이라고 부름)가 있습니다. 리셋이 해제되기 전에 핀을 높거나 낮게 묶는 경우 로직 및 / 또는 부트 로더에게 수행 할 작업을 알려줍니다 (예 : 하나의 스트랩 조합) 칩에 해당 부트 로더를 실행하도록 지시하고 플래시에 데이터가 프로그래밍 될 때까지 uart를 기다리십시오. 스트랩을 다른 방식으로 설정하면 칩 벤더 부트 로더가 아닌 프로그램이 부팅되므로 칩을 현장에서 프로그래밍하거나 프로그램 충돌을 복구 할 수 있습니다. 때로는 플래시를 프로그래밍 할 수있는 순수한 논리 일 수도 있습니다. 이것은 요즘 꽤 흔합니다.
대부분의 마이크로 컨트롤러가 램보다 훨씬 더 많은 플래시를 갖는 이유는 주요 사용 사례는 플래시에서 직접 프로그램을 실행하고 스택 및 변수를 포함하기에 충분한 램만 가지고 있기 때문입니다. 경우에 따라 램에서 프로그램을 실행하여 바로 컴파일하고 플래시에 저장 한 다음 호출하기 전에 복사해야합니다.
편집하다
플래시
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
따라서 이것은 cortex-m0의 예입니다. cortex-ms는이 예제가 진행되는 한 모두 동일하게 작동합니다. 이 예에서 특정 칩은 암 주소 공간의 주소 0x00000000에서 애플리케이션 플래시를, 0x20000000에서 램을 갖습니다.
cortex-m이 부팅되는 방식은 주소 0x0000의 32 비트 워드가 스택 포인터를 초기화하는 주소입니다. 이 예제에는 많은 스택이 필요하지 않으므로 0x20001000으로 충분할 것입니다. 분명히 그 주소 아래에 램이 있어야합니다 (팔이 밀리는 방식, 먼저 빼는 것이므로 0x20001000을 설정하면 스택의 첫 번째 항목은 주소 0x2000FFFC입니다) 0x2000FFFC를 사용할 필요는 없습니다). 주소 0x0004의 32 비트 워드는 재설정 핸들러의 주소이며 기본적으로 재설정 후 실행되는 첫 번째 코드입니다. 그런 다음 해당 cortex m 코어 및 칩, 128 또는 256만큼 많은 인터럽트 및 이벤트 핸들러가 있습니다. 사용하지 않으면 테이블을 설정할 필요가 없습니다. 시연을 위해 몇 가지를 던졌습니다. 목적.
이 예제에서는 .data 또는 .bss를 처리 할 필요가 없습니다. 코드를 보면 해당 세그먼트에 아무것도 없다는 것을 알고 있기 때문입니다. 내가 그것을 처리한다면, 그리고 잠시 후에 것입니다.
따라서 스택은 설정, 확인, .data 처리, 확인, .bss, 확인이므로 C 부트 스트랩 작업이 완료되고 C에 대한 진입 함수로 분기 될 수 있습니다. 일부 컴파일러는 함수를 볼 때 추가 정크를 추가하기 때문에 main () 및 main으로가는 길에 정확한 이름을 사용하지 않습니다. 여기서 C 항목으로 notmain ()을 사용했습니다. 따라서 리셋 핸들러는 notmain ()을 호출 한 다음 notmain ()이 반환 할 때 무한 루프 인 멈춤으로 이름이 잘못 지정 될 수 있습니다.
나는 많은 사람들이 도구를 마스터하는 것을 굳게 믿지만, 당신이 찾을 수있는 것은 앱이나 웹 페이지를 만드는 것처럼 원격으로 제한되지 않은 거의 완전한 자유 때문에 각각의 베어 메탈 개발자가 자신의 일을한다는 것입니다. . 그들은 다시 자신의 일을합니다. 나는 나만의 부트 스트랩 코드와 링커 스크립트를 선호한다. 다른 사람들은 툴체인에 의존하거나 대부분의 작업이 다른 사람에 의해 수행되는 공급 업체 샌드 박스에서 게임을합니다.
따라서 gnu 도구를 사용하여 조립, 컴파일 및 연결합니다.
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
그렇다면 부트 로더는 물건이 어디에 있는지 어떻게 알 수 있습니까? 컴파일러가 작업을 수행했기 때문입니다. 첫 번째 경우 어셈블러는 flash.s에 대한 코드를 생성했으며 그렇게하면 레이블이 어디에 있는지 (레이블은 함수 이름이나 변수 이름과 같은 주소 일뿐입니다) 바이트를 계산하고 벡터를 채울 필요가 없습니다. 테이블을 수동으로 레이블 이름을 사용하고 어셈블러가 대신했습니다. 이제 reset이 주소 0x14인지 어셈블러가 벡터 테이블에 0x15를 넣은 이유는 무엇입니까? 글쎄, 이것은 cortex-m이며 부팅되고 엄지 모드에서만 실행됩니다. Thumb 모드로 분기하는 경우 주소로 분기 할 때 ARM을 사용하면 arm 모드가 재설정 된 경우 lsbit를 설정해야합니다. 따라서 항상 해당 비트 세트가 필요합니다. 도구를 알고 레이블이 벡터 테이블에 그대로 사용되거나 분기 또는 기타로 사용되는 경우 레이블 앞에 .thumb_func를 넣어서 알고 있습니다. 툴체인은 lsbit을 설정하는 것을 알고 있습니다. 여기 0x14 | 1 = 0x15가 있습니다. 행 아웃도 마찬가지입니다. 이제 디스어셈블러는 notmain () 호출에 대해 0x1D를 표시하지 않지만 도구가 명령을 올바르게 작성했는지 걱정하지 마십시오.
이제 코드가 메인이 아니고 해당 로컬 변수가 사용되지 않고 죽은 코드입니다. 컴파일러는 y가 설정되었지만 사용되지 않았다고 말함으로써 그 사실에 대해 언급합니다.
주소 공간을 주목하십시오.이 모든 것은 주소 0x0000에서 시작하여 거기에서 벡터 테이블이 올바르게 배치되고 .text 또는 프로그램 공간이 올바르게 배치됩니다. 도구를 알면 일반적인 실수는 바로 그 권리를 얻지 못하고 추락하고 열심히 태워 버리는 것입니다. IMO는 처음 부팅하기 직전에 물건을 놓기 위해 분해해야합니다. 적절한 장소에 물건이 있으면 매번 점검 할 필요는 없습니다. 새 프로젝트 나 중단 된 경우에만 해당됩니다.
이제 일부 사람들에게 놀라운 것은 두 컴파일러가 동일한 입력에서 동일한 출력을 생성 할 것으로 기대할 이유가 없다는 것입니다. 또는 다른 설정을 가진 동일한 컴파일러조차도. clang을 사용하여 llvm 컴파일러를 사용하여 최적화 여부에 관계 없이이 두 가지 출력을 얻습니다.
llvm / clang 최적화
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
최적화되지 않은
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
컴파일러가 덧셈을 최적화했다는 거짓말이지만 변수에 대해 스택에 두 항목을 할당했습니다. 이것은 로컬 변수이기 때문에 램에 있지만 고정 주소가 아닌 스택에 있으면 전역에서 볼 수 있습니다. 변화. 그러나 컴파일러는 컴파일 타임에 y를 계산할 수 있고 런타임에 계산할 이유가 없으므로 x에 할당 된 스택 공간에 1을, y에 할당 된 스택 공간에 2를 간단히 배치했습니다. 컴파일러는 변수 y의 경우 stack plus 0을, 변수 x의 경우 stack plus 4를 선언하는 내부 테이블로이 공간을 "할당"합니다. 컴파일러는 구현하는 코드가 C 표준 또는 C 프로그래머의 표준을 준수하는 한 원하는 모든 작업을 수행 할 수 있습니다. 컴파일러가 함수 기간 동안 x를 스택 + 4에 두어야 할 이유가 없습니다.
어셈블러에 함수 더미를 추가하면
.thumb_func
.globl dummy
dummy:
bx lr
그리고 전화 해
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
출력 변경
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
이제 중첩 함수가 있으므로 notmain 함수는 리턴 주소를 보존해야 중첩 호출의 리턴 주소를 파악할 수 있습니다. 팔이 x86 또는 다른 것들과 같은 스택을 잘 사용하면 반환을 위해 레지스터를 사용하기 때문입니다 ... 아직 스택을 사용하지만 다르게 사용합니다. 이제 왜 r4를 눌렀습니까? 글쎄, 호출 규칙은 오래 전에 스택을 하나의 워드 경계인 32 비트 대신 64 비트 (2 워드) 경계에 정렬하도록 변경했습니다. 따라서 스택 정렬을 유지하기 위해 무언가를 밀어야하므로 컴파일러는 어떤 이유로 든 r4를 임의로 선택했지만 이유는 중요하지 않습니다. 이 대상에 대한 호출 규칙에 따라 r4에 튀기는 것은 버그 일 것입니다. 함수 호출에서 r4를 클로버하지 말고 r0에서 r3까지 클로버 할 수 있습니다. r0은 반환 값입니다. 꼬리 최적화를하고있는 것 같습니다.
그러나 우리는 x와 y 수학이 더미 함수에 전달되는 2의 하드 코딩 된 값에 최적화되어 있음을 알 수 있습니다 (더미는 별도의 파일,이 경우 asm으로 코딩되어 컴파일러가 함수 호출을 완전히 최적화하지 못합니다) notmain.c에서 C로 반환 된 더미 함수가있는 경우 옵티마이 저는 x, y 및 더미 함수 호출을 모두 제거 / 쓸모없는 코드이기 때문에 제거했을 것입니다).
또한 flash.s 코드가 커졌기 때문에 notmain은 그렇지 않으며 툴 체인은 우리를 위해 모든 주소를 패치하여 처리하므로 수동으로 수행하지 않아도됩니다.
참조를 위해 최적화되지 않은 클랑
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
최적화 된 클랑
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
컴파일러 작성자는 스택을 정렬하기 위해 더미 변수로 r7을 사용하기로 선택했으며 스택 프레임에 아무것도없는 경우에도 r7을 사용하여 프레임 포인터를 만듭니다. 기본적으로 교육은 최적화되었을 수 있습니다. 그러나 팝을 사용하여 세 가지 명령을 반환하지 않았습니다. 아마도 올바른 명령 행 옵션 (프로세서 지정)으로 gcc를 수행 할 수있을 것입니다.
이것은 대부분 나머지 질문에 답해야합니다.
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
나는 지금 세계를 가지고있다. 최적화되지 않으면 .data 또는 .bss로 이동합니다.
최종 출력을보기 전에 itermediate 객체를 볼 수 있습니다
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
이제 이것에서 누락 된 정보가 있지만 무슨 일이 일어나고 있는지에 대한 아이디어를 제공합니다. 링커는 객체를 가져 와서 제공된 정보 (이 경우 flash.ld)와 함께 연결하여 .text 및. 데이터 등이 있습니다. 컴파일러는 그러한 것들을 알지 못하며, 제시된 코드에만 집중할 수 있으며 외부는 링커가 연결을 채우기 위해 구멍을 남겨 두어야합니다. 모든 데이터는 그러한 것들을 함께 연결하는 방법을 남겨 두어야하므로 컴파일러와 디스어셈블러가 알지 못하기 때문에 모든 주소는 여기서 0을 기준으로합니다. 링커가 물건을 배치하는 데 사용하는 다른 정보는 여기에 표시되지 않습니다. 여기의 코드는 위치 독립적이므로 링커가 작업을 수행 할 수 있습니다.
그런 다음 연결된 출력의 분해를 확인하십시오.
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
컴파일러는 기본적으로 램에 두 개의 32 비트 변수를 요청했습니다. 하나는 초기화하지 않았기 때문에 .bss에 있으므로 0으로 초기화한다고 가정합니다. 다른 하나는 선언시 초기화했기 때문에 .data입니다.
이제는 전역 변수이기 때문에 다른 함수가 변수를 수정할 수 있다고 가정합니다. 컴파일러는 notmain이 호출 될 수있는 시점에 대해 가정하지 않으므로 y = x + 1 수학으로 볼 수있는 것과 최적화 할 수 없으므로 런타임을 수행해야합니다. 램에서 두 변수를 추가하고 다시 저장해야합니다.
이제 분명히이 코드가 작동하지 않습니다. 왜? 여기에 표시된 내 부트 스트랩은 notmain을 호출하기 전에 램을 준비하지 않기 때문에 칩이 깨어 났을 때 0x20000000 및 0x20000004에있는 쓰레기는 y와 x에 사용될 것입니다.
여기에 표시하지 않습니다. .data 및 .bss에 대한 더 긴 바람이 부딪 치는 부분을 읽을 수 있으며 베어 메탈 코드에서 왜 필요하지 않은지 알 수 있지만 다른 사람이 올바르게하기를 기대하기보다는 도구를 마스터해야한다고 생각하는 경우 .. .
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
링커 스크립트와 부트 스트랩은 다소 컴파일러마다 다르므로 한 컴파일러의 한 버전에 대해 배우는 모든 것이 다음 버전이나 다른 컴파일러와 관련이있을 수 있지만 .data 및 .bss 준비에 많은 노력을 기울이지 않는 또 다른 이유 이 게으른 사람이 되려면 :
unsigned int x=1;
나는 오히려 오히려 이것을 할 것입니다
unsigned int x;
...
x = 1;
컴파일러가 .text에 넣도록하십시오. 때로는 플래시를 절약하여 때로는 더 많이 태우기도합니다. 툴체인 버전이나 한 컴파일러에서 다른 컴파일러로 프로그래밍하고 포팅하는 것이 가장 쉽습니다. 훨씬 더 안정적이며 오류가 적습니다. 그러나 C 표준을 준수하지 않습니다.
이 정적 글로벌을 만들면 어떻게 될까요?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
잘
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
분명히 이러한 변수는 다른 코드로 수정할 수 없으므로 컴파일러는 컴파일 타임에 이전과 마찬가지로 죽은 코드를 최적화 할 수 있습니다.
최적화되지 않은
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
로컬에 스택을 사용하는이 컴파일러는 이제 전역에 램을 사용하며 작성된 .data 또는 .bss를 올바르게 처리하지 않아 작성된 코드가 손상되었습니다.
그리고 우리가 분해에서 볼 수없는 마지막 것.
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
x를 0x12345678으로 사전 초기화하도록 변경했습니다. 내 링커 스크립트 (이것은 gnu ld 용)는 bob 일에 이것을 가지고 있습니다. 링커에게 최종 장소가 ted 주소 공간에 있기를 원하지만 바이너리 공간에 ted 주소 공간에 저장하면 누군가가 당신을 위해 이동할 것입니다. 그리고 우리는 그것이 일어난 것을 볼 수 있습니다. 이것은 인텔 16 진 형식입니다. 우리는 0x12345678을 볼 수 있습니다
:0400480078563412A0
이진의 플래시 주소 공간에 있습니다.
readelf도 이것을 보여줍니다
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
가상 주소가 0x20000004이고 실제가 0x48 인 LOAD 라인