programing tip

ARM에서 SP (스택) 및 LR이란 무엇입니까?

itbloger 2020. 11. 8. 09:31
반응형

ARM에서 SP (스택) 및 LR이란 무엇입니까?


정의를 반복해서 읽고 있는데 여전히 ARM에서 SP와 LR이 무엇인지 알 수 없습니까? 나는 PC (다음 명령어의 주소를 보여줌)를 이해하고, SP와 LR은 아마도 비슷하지만 그것이 무엇인지 이해하지 못합니다. 저 좀 도와 주 시겠어요?

편집 : 예를 들어 설명 할 수 있다면 훌륭 할 것입니다.

편집 : 마침내 LR이 무엇인지 알아 냈지만 여전히 SP가 무엇인지 얻지 못했습니다.


LR은 함수 호출에 대한 반환 주소를 보유하는 데 사용되는 링크 레지스터 입니다.

SP는 스택 포인터입니다. 스택은 일반적으로 함수 호출에서 "자동"변수와 컨텍스트 / 매개 변수를 유지하는 데 사용됩니다. 개념적으로 "스택"은 데이터를 "더미"하는 장소로 생각할 수 있습니다. 한 데이터를 다른 데이터 위에 계속 "스택"하고 스택 포인터는 데이터 "스택"이 얼마나 높은지 알려줍니다. "스택"의 "상단"에서 데이터를 제거하고 더 짧게 만들 수 있습니다.

ARM 아키텍처 참조에서 :

SP, 스택 포인터

레지스터 R13은 활성 스택에 대한 포인터로 사용됩니다.

Thumb 코드에서 대부분의 명령어는 SP에 액세스 할 수 없습니다. SP에 액세스 할 수있는 유일한 명령어는 SP를 스택 포인터로 사용하도록 설계된 명령어입니다. 스택 포인터 이외의 용도로 SP를 사용하는 것은 더 이상 사용되지 않습니다. 참고 스택 포인터 이외의 용도로 SP를 사용하면 운영 체제, 디버거 및 기타 소프트웨어 시스템의 요구 사항이 깨져서 오작동을 일으킬 수 있습니다.

LR, 링크 레지스터

레지스터 R14는 서브 루틴에서 반환 주소를 저장하는 데 사용됩니다. 다른 경우에는 LR을 다른 용도로 사용할 수 있습니다.

BL 또는 BLX 명령어가 서브 루틴 호출을 수행 할 때 LR은 서브 루틴 반환 주소로 설정됩니다. 서브 루틴 리턴을 수행하려면 LR을 프로그램 카운터에 다시 복사하십시오. 일반적으로 BL 또는 BLX 명령어로 서브 루틴을 입력 한 후 다음 두 가지 방법 중 하나로 수행됩니다.

• BX LR 명령으로 반환합니다.

• 서브 루틴 항목에서 PUSH {, LR} 형식의 명령어를 사용하여 LR을 스택에 저장하고 일치하는 명령어를 사용하여 다음을 반환합니다. POP {, PC} ...

이 링크는 사소한 서브 루틴의 예를 제공합니다.

다음은 호출 전에 레지스터를 스택에 저장 한 다음 다시 팝하여 콘텐츠를 복원하는 방법의 예입니다.


SP는 r13을 입력하기위한 바로 가기 인 스택 레지스터입니다. LR은 r14의 바로 가기 링크 레지스터입니다. 그리고 PC는 r15를 입력하는 바로 가기 프로그램 카운터입니다.

분기 링크 명령 bl이라고하는 호출을 수행하면 리턴 주소가 링크 레지스터 인 r14에 배치됩니다. 프로그램 카운터 pc는 분기하려는 주소로 변경됩니다.

예를 들어, 포 그라운드에서 실행할 때와 다른 스택을 사용하는 경우와 같이 인터럽트를 치면 전통적인 ARM 코어 (cortex-m 시리즈는 예외 임)에 몇 개의 스택 포인터가 있습니다. 코드를 변경할 필요가 없습니다. sp 또는 r13은 일반적으로 하드웨어가 자동으로 전환했으며 명령어를 디코딩 할 때 올바른 스위치를 사용합니다.

기존 ARM 명령어 세트 (엄지 손가락 아님)를 사용하면 스택을 하위 주소에서 상위 주소로 늘리거나 상위 주소에서 하위 주소로 확장 할 때 스택을 자유롭게 사용할 수 있습니다. 컴파일러와 대부분의 사람들은 스택 포인터를 높게 설정하고 높은 주소에서 낮은 주소로 성장하도록합니다. 예를 들어 0x20000000에서 0x20008000까지의 ram이있을 수 있습니다. 링커 스크립트를 설정하여 0x20000000을 실행 / 사용하도록 프로그램을 빌드하고 시작 코드에서 스택 포인터를 0x20008000으로 설정합니다. 최소한 시스템 / 사용자 스택 포인터는 나누어야합니다. 필요한 경우 다른 스택을위한 메모리.

스택은 단지 메모리입니다. 프로세서에는 일반적으로 PC 기반이고 일부는 스택 기반 인 특수 메모리 읽기 / 쓰기 명령이 있습니다. 최소한 스택은 일반적으로 push 및 pop으로 이름이 지정되지만 반드시 그럴 필요는 없습니다 (기존의 arm 지침과 마찬가지로).

http://github.com/lsasim으로 이동 하면 교육 프로세서를 만들고 어셈블리 언어 자습서가 있습니다. 어딘가에서 스택에 대한 토론을 진행합니다. 팔 프로세서는 아니지만 이야기는 팔이나 대부분의 다른 프로세서에서 이해하려는 내용으로 직접 번역해야합니다.

예를 들어 프로그램에 필요한 20 개의 변수가 있지만 특수 목적인 레지스터 중 최소 3 개 (sp, lr, pc)를 뺀 16 개의 레지스터 만 있다고 가정 해 보겠습니다. 일부 변수를 ram에 유지해야합니다. r5에 램에 보관하고 싶지 않을 정도로 자주 사용하는 변수가 있다고 가정 해 보겠습니다.하지만 코드의 한 섹션이있어 무언가를 수행하기 위해 또 다른 레지스터가 필요하고 r5는 사용되지 않습니다. r5를 다음에 저장할 수 있습니다. r5를 다른 용도로 재사용하는 동안 최소한의 노력으로 스택을 만든 다음 나중에 쉽게 복원합니다.

기존 (처음으로 돌아가는 것은 아님) arm 구문 :

...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...

stm은 여러 개의 레지스터를 저장하므로 한 번에 하나 이상의 레지스터를 하나의 명령어에 모두 저장할 수 있습니다.

db는 이전에 감소를 의미하며 이는 높은 주소에서 낮은 주소로 아래로 이동하는 스택입니다.

여기서 r13 또는 sp를 사용하여 스택 포인터를 나타낼 수 있습니다. 이 특정 명령어는 스택 작업에 ​​국한되지 않고 다른 용도로 사용할 수 있습니다.

! r13 레지스터를 완료 한 후 새 주소로 업데이트하는 것을 의미합니다. 여기서 다시 stm을 스택이 아닌 작업에 사용할 수 있으므로 기본 주소 레지스터를 변경하지 않으려면! 이 경우 꺼져 있습니다.

그런 다음 대괄호 {} 안에 저장할 레지스터를 쉼표로 구분하여 나열하십시오.

ldmia는 그 반대이고, ldm은 다중로드를 의미합니다. ia는 이후 증분을 의미하고 나머지는 stm과 동일합니다.

따라서 스택 포인터가 0x20008000에 있으면 목록에 32 비트 레지스터가 하나있는 것으로보고 stmdb 명령어를 눌렀을 때 r13의 값을 사용하기 전에 감소하므로 0x20007FFC는 r5를 메모리의 0x20007FFC에 쓰고 값을 저장합니다 r13의 0x20007FFC. 나중에 ldmia 명령어 r13에 0x20007FFC가있을 때 버그가 없다고 가정하면 목록 r5에 단일 레지스터가 있습니다. 그래서 0x20007FFC에서 메모리를 읽고 그 값을 r5에 넣고 ia는 증가를 의미하므로 0x20007FFC는 하나의 레지스터 크기를 0x20008000으로 증가시키고! 명령을 완료하려면 해당 번호를 r13에 쓰십시오.

Why would you use the stack instead of just a fixed memory location? Well the beauty of the above is that r13 can be anywhere it could be 0x20007654 when you run that code or 0x20002000 or whatever and the code still functions, even better if you use that code in a loop or with recursion it works and for each level of recursion you go you save a new copy of r5, you might have 30 saved copies depending on where you are in that loop. and as it unrolls it puts all the copies back as desired. with a single fixed memory location that doesnt work. This translates directly to C code as an example:

void myfun ( void )
{
   int somedata;
}

이와 같은 C 프로그램에서 somedata 변수는 스택에 있으며 myfun을 재귀 적으로 호출하면 재귀의 깊이에 따라 somedata에 대한 값의 여러 복사본을 갖게됩니다. 또한 해당 변수는 함수 내에서만 사용되며 다른 곳에서는 필요하지 않으므로 프로그램 수명 동안 해당 변수에 대한 시스템 메모리 양을 태우고 싶지 않을 수도 있습니다. 그 기능이 아닙니다. 그것이 스택이 사용되는 것입니다.

스택에서 전역 변수를 찾을 수 없습니다.

돌아 가지...

이 함수를 구현하고 호출하고 싶다고 가정하면 myfun 함수를 호출 할 때 사용하는 코드 / 함수가있을 것입니다. myfun 함수는 어떤 작업을 수행 할 때 r5 및 r6을 사용하려고하지만 누군가가 호출 한 내용이 r5 및 r6을 사용하는 것을 폐기하고 싶지 않으므로 myfun () 기간 동안 스택에 해당 레지스터를 저장하고 싶을 것입니다. 마찬가지로 분기 링크 명령어 (bl)와 링크 레지스터 lr (r14)를 살펴보면 링크 레지스터가 하나뿐입니다. 함수에서 함수를 호출하면 각 호출에서 링크 레지스터를 저장해야합니다. 그렇지 않으면 반환 할 수 없습니다. .

...
bl myfun
    <--- the return from my fun returns here
...


myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
   <---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.

So hopefully you can see both the stack usage and link register. Other processors do the same kinds of things in a different way. for example some will put the return value on the stack and when you execute the return function it knows where to return to by pulling a value off of the stack. Compilers C/C++, etc will normally have a "calling convention" or application interface (ABI and EABI are names for the ones ARM has defined). if every function follows the calling convention, puts parameters it is passing to functions being called in the right registers or on the stack per the convention. And each function follows the rules as to what registers it does not have to preserve the contents of and what registers it has to preserve the contents of then you can have functions call functions call functions and do recursion and all kinds of things, so long as the stack does not go so deep that it runs into the memory used for globals and the heap and such, you can call functions and return from them all day long. The above implementation of myfun is very similar to what you would see a compiler produce.

ARM has many cores now and a few instruction sets the cortex-m series works a little differently as far as not having a bunch of modes and different stack pointers. And when executing thumb instructions in thumb mode you use the push and pop instructions which do not give you the freedom to use any register like stm it only uses r13 (sp) and you cannot save all the registers only a specific subset of them. the popular arm assemblers allow you to use

push {r5,r6}
...
pop {r5,r6}

in arm code as well as thumb code. For the arm code it encodes the proper stmdb and ldmia. (in thumb mode you also dont have the choice as to when and where you use db, decrement before, and ia, increment after).

No you absolutly do not have to use the same registers and you dont have to pair up the same number of registers.

push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}

assuming there is no other stack pointer modifications in between those instructions if you remember the sp is going to be decremented 12 bytes for the push lets say from 0x1000 to 0x0FF4, r5 will be written to 0xFF4, r6 to 0xFF8 and r7 to 0xFFC the stack pointer will change to 0x0FF4. the first pop will take the value at 0x0FF4 and put that in r2 then the value at 0x0FF8 and put that in r3 the stack pointer gets the value 0x0FFC. later the last pop, the sp is 0x0FFC that is read and the value placed in r1, the stack pointer then gets the value 0x1000, where it started.

The ARM ARM, ARM Architectural Reference Manual (infocenter.arm.com, reference manuals, find the one for ARMv5 and download it, this is the traditional ARM ARM with ARM and thumb instructions) contains pseudo code for the ldm and stm ARM istructions for the complete picture as to how these are used. Likewise well the whole book is about the arm and how to program it. Up front the programmers model chapter walks you through all of the registers in all of the modes, etc.

If you are programming an ARM processor you should start by determining (the chip vendor should tell you, ARM does not make chips it makes cores that chip vendors put in their chips) exactly which core you have. Then go to the arm website and find the ARM ARM for that family and find the TRM (technical reference manual) for the specific core including revision if the vendor has supplied that (r2p0 means revision 2.0 (two point zero, 2p0)), even if there is a newer rev, use the manual that goes with the one the vendor used in their design. Not every core supports every instruction or mode the TRM tells you the modes and instructions supported the ARM ARM throws a blanket over the features for the whole family of processors that that core lives in. Note that the ARM7TDMI is an ARMv4 NOT an ARMv7 likewise the ARM9 is not an ARMv9. ARMvNUMBER is the family name ARM7, ARM11 without a v is the core name. The newer cores have names like Cortex and mpcore instead of the ARMNUMBER thing, which reduces confusion. Of course they had to add the confusion back by making an ARMv7-m (cortex-MNUMBER) and the ARMv7-a (Cortex-ANUMBER) which are very different families, one is for heavy loads, desktops, laptops, etc the other is for microcontrollers, clocks and blinking lights on a coffee maker and things like that. google beagleboard (Cortex-A) and the stm32 value line discovery board (Cortex-M) to get a feel for the differences. Or even the open-rd.org board which uses multiple cores at more than a gigahertz or the newer tegra 2 from nvidia, same deal super scaler, muti core, multi gigahertz. A cortex-m barely brakes the 100MHz barrier and has memory measured in kbytes although it probably runs of a battery for months if you wanted it to where a cortex-a not so much.

sorry for the very long post, hope it is useful.

참고URL : https://stackoverflow.com/questions/8236959/what-are-sp-stack-and-lr-in-arm

반응형