x86 어셈블리의 레지스터에 사용되는 푸시 / 팝 명령어의 기능은 무엇입니까?


103

어셈블러에 대해 읽을 때 나는 종종 프로세서의 특정 레지스터를 푸시 하고 나중에 이전 상태로 복원하기 위해 다시 한다고 쓰는 사람들을 만납니다 .

  • 레지스터를 어떻게 밀 수 있습니까? 어디로 밀려나? 왜 이것이 필요한가요?
  • 이것이 단일 프로세서 명령으로 귀결됩니까 아니면 더 복잡합니까?

4
경고 : 현재의 모든 답변은 Intel의 어셈블리 구문으로 제공됩니다. 예를 들어, AT & T 구문 푸시 팝 같은 포스트 픽스를 사용하여 b, w, l, 또는 q상기 메모리의 크기를 나타내는 조작 될 수있다. 예 : pushl %eaxandpopl %eax
Hawken 2012

5
@hawken AT & T 구문 (특히 가스)을 삼킬 수있는 대부분의 어셈블러에서 피연산자 크기를 피연산자 크기에서 추론 할 수 있으면 크기 접미사를 생략 할 수 있습니다. 이것은 %eax항상 32 비트 크기이므로 귀하가 제공 한 예의 경우입니다 .
Gunther Piez

답변:


155

값을 푸시 하는 것은 (레지스터에 반드시 저장되지는 ​​않음) 스택에 쓰는 것을 의미합니다.

팝핑 은 스택의 맨 위에있는 모든 것을 레지스터 복원하는 것을 의미 합니다. 다음은 기본 지침입니다.

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

5
push 및 pop에 대한 명시 적 피연산자는 r/m등록뿐 아니라이므로 push dword [esi]. 또는 pop dword [esp]동일한 값을로드 한 다음 동일한 주소에 다시 저장할 수도 있습니다. ( github.com/HJLebbink/asm-dude/wiki/POP ). 나는 당신이 "반드시 레지스터는 아니다"라고 말했기 때문에 이것을 언급 할뿐입니다.
Peter Cordes

2
pop메모리 영역으로 도 들어갈 수 있습니다 .pop [0xdeadbeef]
SS Anne

안녕하세요. push / pop과 pushq / popq의 차이점은 무엇인가요? 저는 macos / intel을 사용합니다
SteakOverflow 2019

47

레지스터를 푸시하는 방법은 다음과 같습니다. 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레지스터의 현재 주소에 저장합니다 .

@vavan이 수정 요청을 보냈습니다
jgh fun-run

39

어디로 밀려나?

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더 구체적이기 때문에 단일 명령어가 다른 명령어의 동등한 조합보다 빠를 수도 있습니다 .

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


4
당신은 방법에 대해 추측 할 필요가 없습니다 push/ pop마이크로 연산으로 디코드. 성능 카운터 덕분에 실험적 테스트가 가능하며 Agner Fog는이를 수행하고 지침 표를 게시했습니다 . 펜티엄 M 나중에 CPU는 단일 UOP가 push/ pop스택 엔진 덕분에 (참조 Agner의 microarch의 PDF를). 여기에는 Intel / AMD 특허 공유 계약 덕분에 최신 AMD CPU가 포함됩니다.
Peter Cordes

@PeterCordes 대박! 그렇다면 성능 카운터는 마이크로 작업을 계산하기 위해 인텔에 의해 문서화됩니까?
치로 틸리는郝海东冠状病六四事件法轮功

또한 regs에서 유출 된 로컬 변수는 실제로 사용중인 경우 L1 캐시에서 여전히 뜨겁습니다. 그러나 레지스터에서 읽는 것은 사실상 무료이며 지연 시간이 없습니다. 따라서 용어를 정의하려는 방법에 따라 L1 캐시보다 훨씬 빠릅니다. 읽기 전용 로컬이 스택에 유출 된 경우 주요 비용은 추가로드 uop (때로는 메모리 피연산자, 때로는 별도의 mov로드 포함)입니다. 유출 된 상수가 아닌 변수의 경우 저장 전달 왕복은 많은 추가 지연 시간입니다 (직접 전달에 비해 추가 ~ 5c이며 저장 명령은 저렴하지 않습니다).
Peter Cordes

예, 몇 가지 다른 파이프 라인 단계 (발행 / 실행 / 폐기)에서 총 uop에 대한 카운터가 있으므로 융합 도메인 또는 융합되지 않은 도메인을 계산할 수 있습니다. 예를 들어이 답변 을 참조하십시오 . 지금 그 대답을 다시 작성한다면 ocperf.py래퍼 스크립트를 사용 하여 카운터에 대한 쉬운 기호 이름을 얻습니다.
Peter Cordes

24

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

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 구문입니다.

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


5
예, 이러한 시퀀스는 푸시 / 팝을 올바르게 에뮬레이트합니다. (푸시 / 팝 제외는 플래그에 영향을주지 않음).
Peter Cordes 2016 년

2
플래그에 대한 / 효과를 더 잘 에뮬레이션하려면 / lea rsp, [rsp±8]대신 사용 하는 것이 좋습니다 . addsubpushpop
Ruslan

13

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

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

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

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

  1. CPU 레지스터 및 상수.
  2. 함수 또는 절차에 대한 주소 반환
  3. 함수 / 프로 시저 입력 / 출력 변수
  4. 함수 / 프로 시저 지역 변수.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.