x86 어셈블리의 레지스터에 사용되는 푸시 / 팝 명령어의 기능은 무엇입니까?
어셈블러에 대해 읽을 때 나는 종종 프로세서의 특정 레지스터를 푸시 하고 나중에 이전 상태로 복원하기 위해 다시 팝 한다고 쓰는 사람들을 만납니다 .
- 레지스터를 어떻게 밀 수 있습니까? 어디로 밀려나? 왜 이것이 필요한가요?
- 이것이 단일 프로세서 명령으로 귀결됩니까 아니면 더 복잡합니까?
값을 푸시 (반드시 레지스터에 저장되지 않음)하는 것은 스택에 쓰는 것을 의미합니다.
팝핑 은 스택 맨 위에있는 모든 것을 레지스터 로 복원하는 것을 의미 합니다. 다음은 기본 지침입니다.
push 0xdeadbeef ; push a value to the stack
pop eax ; eax is now 0xdeadbeef
; swap contents of registers
push eax
mov eax, ebx
pop ebx
레지스터를 푸시하는 방법은 다음과 같습니다. x86에 대해 이야기하고 있다고 가정합니다.
push ebx
push eax
스택에 푸시됩니다. ESP
레지스터 의 값은 x86 시스템에서 스택이 아래로 커짐에 따라 푸시 된 값의 크기로 감소합니다.
가치를 보존하는 것이 필요합니다. 일반적인 사용법은
push eax ; preserve the value of eax
call some_method ; some method is called which will put return value in eax
mov edx, eax ; move the return value to edx
pop eax ; restore original eax
A push
는 x86의 단일 명령어로 내부적으로 두 가지 작업을 수행합니다.
- 푸시 된 값을
ESP
레지스터의 현재 주소에 저장합니다 . - 감분
ESP
푸시 값의 크기를 레지스터.
어디로 밀려나?
esp - 4
. 더 정확하게:
esp
4를 뺀다- 값이 푸시됩니다
esp
pop
이것을 뒤집습니다.
System V ABI는 rsp
프로그램이 실행될 때 적절한 스택 위치를 가리 키도록 Linux에 지시 합니다. 프로그램이 시작될 때 기본 등록 상태 (asm, linux)는 무엇입니까? 일반적으로 사용해야하는 것입니다.
레지스터를 어떻게 밀 수 있습니까?
최소 GNU GAS 예 :
.data
/* .long takes 4 bytes each. */
val1:
/* Store bytes 0x 01 00 00 00 here. */
.long 1
val2:
/* 0x 02 00 00 00 */
.long 2
.text
/* Make esp point to the address of val2.
* Unusual, but totally possible. */
mov $val2, %esp
/* eax = 3 */
mov $3, %ea
push %eax
/*
Outcome:
- esp == val1
- val1 == 3
esp was changed to point to val1,
and then val1 was modified.
*/
pop %ebx
/*
Outcome:
- esp == &val2
- ebx == 3
Inverses push: ebx gets the value of val1 (first)
and then esp is increased back to point to val2.
*/
The above on GitHub with runnable assertions.
Why is this needed?
It is true that those instructions could be easily implemented via mov
, add
and sub
.
They reason they exist, is that those combinations of instructions are so frequent, that Intel decided to provide them for us.
The reason why those combinations are so frequent, is that they make it easy to save and restore the values of registers to memory temporarily so they don't get overwritten.
To understand the problem, try compiling some C code by hand.
A major difficulty, is to decide where each variable will be stored.
Ideally, all variables would fit into registers, which is the fastest memory to access (currently about 100x faster than RAM).
But of course, we can easily have more variables than registers, specially for the arguments of nested functions, so the only solution is to write to memory.
We could write to any memory address, but since the local variables and arguments of function calls and returns fit into a nice stack pattern, which prevents memory fragmentation, that is the best way to deal with it. Compare that with the insanity of writing a heap allocator.
Then we let compilers optimize the register allocation for us, since that is NP complete, and one of the hardest parts of writing a compiler. This problem is called register allocation, and it is isomorphic to graph coloring.
When the compiler's allocator is forced to store things in memory instead of just registers, that is known as a spill.
Does this boil down to a single processor instruction or is it more complex?
All we know for sure is that Intel documents a push
and a pop
instruction, so they are one instruction in that sense.
Internally, it could be expanded to multiple microcodes, one to modify esp
and one to do the memory IO, and take multiple cycles.
But it is also possible that a single push
is faster than an equivalent combination of other instructions, since it is more specific.
This is mostly un(der)documented:
- Peter Cordes mentions that techniques described at http://agner.org/optimize/microarchitecture.pdf suggest that
push
andpop
take one single micro operation. - Johan mentions that since the Pentium M Intel uses a "stack engine", which stores precomputed esp+regsize and esp-regsize values, allowing push and pop to execute in a single uop. Also mentioned at: https://en.wikipedia.org/wiki/Stack_register
- What is Intel microcode?
- https://security.stackexchange.com/questions/29730/processor-microcode-manipulation-to-change-opcodes
- How many CPU cycles are needed for each assembly instruction?
Pushing and popping registers are behind the scenes equivalent to this:
push reg <= same as => sub $8,%rsp # subtract 8 from rsp
mov reg,(%rsp) # store, using rsp as the address
pop reg <= same as=> mov (%rsp),reg # load, using rsp as the address
add $8,%rsp # add 8 to the rsp
Note this is x86-64 At&t syntax.
Used as a pair, this lets you save a register on the stack and restore it later. There are other uses, too.
Almost all CPUs use stack. The program stack is LIFO technique with hardware supported manage.
Stack is amount of program (RAM) memory normally allocated at the top of CPU memory heap and grow (at PUSH instruction the stack pointer is decreased) in opposite direction. A standard term for inserting into stack is PUSH and for remove from stack is POP.
Stack is managed via stack intended CPU register, also called stack pointer, so when CPU perform POP or PUSH the stack pointer will load/store a register or constant into stack memory and the stack pointer will be automatic decreased xor increased according number of words pushed or poped into (from) stack.
Via assembler instructions we can store to stack:
- CPU registers and also constants.
- Return addresses for functions or procedures
- Functions/procedures in/out variables
- Functions/procedures local variables.
'Programing' 카테고리의 다른 글
클래스가 Java에서 인터페이스를 구현하는지 확인 (0) | 2020.10.18 |
---|---|
Javascript에서 여러 공백을 모두 제거하고 단일 공백으로 교체하십시오. (0) | 2020.10.18 |
jQuery는 1 div를 제외한 페이지의 아무 곳이나 클릭합니다. (0) | 2020.10.18 |
ASP.NET MVC 프로젝트 이름 바꾸기 오류 (0) | 2020.10.18 |
VB.NET 또는 C #에서 itextsharp dll로 PDF 콘텐츠 읽기 (0) | 2020.10.18 |