[assembly] 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의 단일 명령어로 내부적으로 두 가지 작업을 수행합니다.

  1. 감분 ESP푸시 값의 크기에 따라 레지스터.
  2. 푸시 된 값을 ESP레지스터의 현재 주소에 저장합니다 .


답변

어디로 밀려나?

esp - 4. 더 정확하게:

  • esp 4를 뺀다
  • 값이 푸시됩니다 esp

pop 이것을 뒤집습니다.

System V ABI는 Linux에 rsp 프로그램이 실행될 때 적절한 스택 위치를 가리 키도록 합니다. 프로그램이 시작될 때 기본 등록 상태 (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.
    */

GitHub에서 실행 가능한 어설 션이 있습니다.

왜 이것이 필요한가요?

이러한 명령은를 통해 쉽게 구현할 수 있다는 것은 사실입니다 mov.add 하고 sub.

그들이 존재하는 이유는 이러한 명령 조합이 너무 자주 발생하여 인텔이 우리에게 제공하기로 결정했기 때문입니다.

이러한 조합이 자주 발생하는 이유는 레지스터 값을 일시적으로 메모리에 저장하고 복원하여 덮어 쓰지 않기 때문입니다.

문제를 이해하려면 C 코드를 직접 컴파일 해보십시오.

가장 큰 어려움은 각 변수를 저장할 위치를 결정하는 것입니다.

이상적으로는 모든 변수가 레지스터에 들어가는데 , 이는 액세스하기 가장 빠른 메모리입니다 (현재 RAM보다 약 100 배 빠름 ).

그러나 물론, 특히 중첩 함수의 인수에 대해 레지스터보다 더 많은 변수를 쉽게 가질 수 있으므로 유일한 해결책은 메모리에 쓰는 것입니다.

모든 메모리 주소에 쓸 수 있지만 함수 호출 및 반환의 지역 변수와 인수가 멋진 스택 패턴에 맞기 때문에 메모리 조각화 를 방지 합니다. 때문에이를 처리하는 가장 좋은 방법입니다. 힙 할당자를 작성하는 광기와 비교하십시오.

그런 다음 컴파일러가 NP 완료이고 컴파일러 작성에서 가장 어려운 부분 중 하나이기 때문에 레지스터 할당을 최적화하도록합니다. 이 문제를 레지스터 할당 이라고 하며 그래프 색상 과 동형 입니다.

컴파일러의 할당자가 레지스터 대신 메모리에 항목을 저장하도록 강제하는 경우이를 spill이라고 합니다.

이것이 단일 프로세서 명령으로 요약됩니까 아니면 더 복잡합니까?

우리가 확실히 아는 것은 인텔이 a pushpop명령을 문서화 한다는 것입니다. 따라서 그것들은 그런 의미에서 하나의 명령입니다.

내부적으로는 여러 마이크로 코드로 확장 할 수 있습니다. 하나는 수정 esp하고 다른 하나는 메모리 IO를 수행하며 여러주기를 필요로합니다.

그러나 push더 구체적이기 때문에 단일 명령어가 다른 명령어의 동등한 조합보다 빠를 수도 있습니다 .

이것은 대부분 문서화되지 않았습니다.


답변

푸시 및 팝 레지스터는 다음과 같은 배후에 있습니다.

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

이것은 x86-64 At & t 구문입니다.

쌍으로 사용하면 레지스터를 스택에 저장하고 나중에 복원 할 수 있습니다. 다른 용도도 있습니다.


답변

거의 모든 CPU가 스택을 사용합니다. 프로그램 스택은 관리를 지원하는 하드웨어 가있는 LIFO 기술입니다.

스택은 CPU 메모리 힙의 맨 위에 일반적으로 할당되고 반대 방향으로 증가 (PUSH 명령에서 스택 포인터가 감소 함)되는 프로그램 (RAM) 메모리의 양입니다. 스택에 삽입하는 표준 용어는 PUSH 이고 스택에서 제거 하기위한 표준 용어 는 POP 입니다.

스택은 스택 포인터라고도하는 스택 의도 CPU 레지스터를 통해 관리되므로 CPU가 POP 또는 PUSH를 수행 할 때 스택 포인터는 레지스터 또는 상수를 스택 메모리에로드 / 저장하고 스택 포인터는 푸시 된 단어 수에 따라 자동 감소 x 또는 증가합니다. 또는 스택에서 (에서) 팝되었습니다.

어셈블러 명령어를 통해 스택에 저장할 수 있습니다.

  1. CPU 레지스터 및 상수.
  2. 함수 또는 절차에 대한 주소 반환
  3. 함수 / 프로 시저 입력 / 출력 변수
  4. 함수 / 프로 시저 지역 변수.


답변