기본 포인터와 스택 포인터는 정확히 무엇입니까? 그들은 무엇을 지적합니까?


225

DrawSquare ()가 DrawLine ()을 호출하는 Wikipedia에서 온 이 예제를 사용 하면 ,

대체 텍스트

이 다이어그램은 맨 아래에 높은 주소가 있고 맨 위에는 낮은 주소가 있습니다.

사람이 나를 설명 무엇을 할 수 ebpesp이러한 맥락에있다?

내가 본 것에서 스택 포인터는 항상 스택의 상단을 가리키고 기본 포인터는 현재 함수의 시작을 가리키는 것이라고 말하고 싶습니다. 또는 무엇을?


편집 : 나는 Windows 프로그램의 맥락에서 이것을 의미합니다.

edit2 : 그리고 어떻게 eip작동합니까?

edit3 : MSVC ++에서 다음 코드가 있습니다.

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr  8
hPrevInstance= dword ptr  0Ch
lpCmdLine= dword ptr  10h
nShowCmd= dword ptr  14h

그들 모두는 dwords 인 것처럼 보이므로 각각 4 바이트를 사용합니다. 따라서 hInstance에서 var_4와 4 바이트의 간격이 있음을 알 수 있습니다. 그들은 무엇인가? Wikipedia의 그림에서 볼 수 있듯이 반송 주소라고 가정합니까?


(편집자 주 : Michael의 답변에서 긴 인용문을 제거했습니다.이 질문에는 해당 질문에 속하지 않지만 후속 질문은 편집되었습니다.)

함수 호출의 흐름은 다음과 같습니다.

* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

내 질문 (마지막, 나는 희망한다!)은, 내가 프롤로그의 끝까지 호출하고자하는 함수의 인수를 팝업하는 순간부터 정확히 어떻게됩니까? 나는 ebp, esp가 그 순간에 어떻게 진화하는지 알고 싶다. (나는 이미 프롤로그가 어떻게 작동하는지 이해했다.


23
주목해야 할 한 가지는 스택이 메모리에서 "아래로"커진다는 것입니다. 즉, 스택 포인터를 위로 움직이면 값이 줄어 듭니다.
BS

4
EBP / ESP와 EIP가 수행하는 작업을 차별화하는 한 가지 힌트 : EBP 및 ESP는 데이터를 처리하고 EIP는 코드를 처리합니다.
mmmmmmmm

2
그래프에서 ebp (일반적으로)는 "프레임 포인터"이며, 특히 "스택 포인터"입니다. 이를 통해 스택 포인터 (함수 내에서 자주 변경됨)와 상관없이 [ebp-x]를 통해 로컬에 액세스하고 [ebp + x]를 통해 스택 매개 변수에 일관되게 액세스 할 수 있습니다. 어드레싱은 ESP를 통해 수행되어 다른 작업을 위해 EBP를 비울 수 있지만 디버거는 호출 스택 또는 로컬 값을 알 수 없습니다.
peterchen

4
@ 벤. 냉담하지 않습니다. 일부 컴파일러는 스택 프레임을 힙에 넣습니다. 스택 성장의 개념은 이해하기 쉽게 만드는 개념입니다. 스택의 구현은 무엇이든 가능합니다 (임의의 덩어리를 사용하면 스택의 일부를 덮어 쓰는 핵이 결정적이지 않기 때문에 훨씬 더 어려워집니다).
Martin York

1
두 단어로 : 스택 포인터를 사용하면 푸시 / 팝 작업을 수행 할 수 있습니다 (따라서 푸시 및 팝은 데이터를 넣거나 가져올 위치를 알 수 있습니다). 기본 포인터를 사용하면 코드가 이전에 스택에서 푸시 된 데이터를 독립적으로 참조 할 수 있습니다.
tigrou

답변:


228

esp 당신이 말한대로, 스택의 상단입니다.

ebp일반적으로 esp함수 시작시 설정됩니다 . 함수 매개 변수 및 로컬 변수는에서 상수 오프셋을 각각 더하고 빼서 액세스합니다 ebp. 모든 x86 호출 규칙은 ebp함수 호출에서 유지되는 것으로 정의 합니다. ebp실제로는 이전 프레임의 기본 포인터를 가리 키므로 디버거에서 스택 워킹을 수행하고 로컬 변수가 작동하는 다른 프레임을 볼 수 있습니다.

대부분의 함수 프롤로그는 다음과 같습니다.

push ebp      ; Preserve current frame pointer
mov ebp, esp  ; Create new frame pointer pointing to current stack top
sub esp, 20   ; allocate 20 bytes worth of locals on stack.

그런 다음 나중에 함수에서 다음과 같은 코드를 가질 수 있습니다 (두 로컬 변수가 모두 4 바이트라고 가정)

mov [ebp-4], eax    ; Store eax in first local
mov ebx, [ebp - 8]  ; Load ebx from second local

활성화 할 수있는 FPO 또는 프레임 포인터 생략 최적화는 실제로 이것을 제거 ebp하고 다른 레지스터로 사용 하고 로컬에서 직접 액세스 할 수 esp있지만 디버거는 더 이상 이전 함수 호출의 스택 프레임에 직접 액세스 할 수 없으므로 디버깅이 조금 더 어려워집니다.

편집하다:

업데이트 된 질문의 경우 스택에서 누락 된 두 항목은 다음과 같습니다.

var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
*savedFramePointer = dword ptr 0*
*return address = dword ptr 4*
hInstance = dword ptr  8h
PrevInstance = dword ptr  0C
hlpCmdLine = dword ptr  10h
nShowCmd = dword ptr  14h

함수 호출의 흐름은 다음과 같습니다.

  • 푸시 파라미터 ( hInstance등)
  • 반송 주소를 푸시하는 호출 기능
  • 푸시 ebp
  • 현지인을위한 공간 할당

1
설명 주셔서 감사합니다! 그러나 나는 지금 다소 혼란 스럽다. 함수를 호출하고 프롤로그의 첫 번째 행에 있지만 여전히 단일 행을 실행하지 않았다고 가정 해 봅시다. 그 시점에서 ebp의 가치는 무엇입니까? 스택에는 그 시점에서 푸시 된 인수 외에 다른 것이 있습니까? 감사!
삼키는 엘리시움

3
EBP는 마술처럼 바뀌지 않으므로 함수에 대해 새로운 EBP를 설정하기 전까지는 여전히 발신자 가치가 유지됩니다. 그리고 인수 외에도 스택은 이전 EIP (반환 주소)를 보유합니다
MSalters September

3
좋은 대답입니다. 에필로그에있는 내용을 언급하지 않고는 완료 할 수 없지만 "leave"및 "ret"명령
Calmarius

2
이 이미지는 흐름이 무엇인지 명확히하는 데 도움이 될 것이라고 생각합니다. 또한 스택이 아래쪽으로 자라는 것을 명심하십시오. ocw.cs.pub.ro/courses/_media/so/laboratoare/call_stack.png
Andrei-Niculae Petre

나입니까, 아니면 위의 코드 스 니펫에서 모든 빼기 기호가 누락 되었습니까?
BarbaraKwarc 2012 년

96

ESP는 현재 스택 포인터로, 단어 나 주소를 스택으로 밀거나 뺄 때마다 변경됩니다. EBP는 컴파일러가 ESP를 직접 사용하는 것보다 함수의 매개 변수와 로컬 변수를 추적 할 수있는 편리한 방법입니다.

일반적으로 (이것은 컴파일러마다 다를 수 있습니다), 호출되는 함수에 대한 모든 인수는 호출 함수에 의해 스택에 푸시됩니다 (일반적으로 함수 프로토 타입에서 선언 된 역순으로 변경되지만 이는 다릅니다) . 그런 다음 함수가 호출되어 리턴 주소 (EIP)를 스택으로 푸시합니다.

기능에 진입하면 기존 EBP 값이 스택으로 푸시되고 EBP가 ESP 값으로 설정됩니다. 그런 다음 ESP가 감소하여 (스택이 메모리에서 아래로 커지기 때문에) 함수의 로컬 변수 및 임시 공간을 할당합니다. 그 시점부터 함수를 실행하는 동안 함수에 대한 인수는 함수 호출 전에 푸시 되었기 때문에 EBP에서 양의 오프셋으로 스택에 있고 로컬 변수는 EBP에서 음의 오프셋에 있습니다. (함수 입력 후 스택에 할당 되었기 때문에). 이것이 EBP가 함수 호출 프레임 의 중심을 가리 키기 때문에 프레임 포인터 라고하는 이유 입니다.

종료하면 ESP를 EBP 값으로 설정하고 (스택에서 로컬 변수를 할당 해제하고 스택 맨 위에 EBP 항목을 노출 함) 스택에서 이전 EBP 값을 팝합니다. 그런 다음 함수가 리턴합니다 (반환 주소를 EIP에 넣음).

호출 함수로 되돌아 가면 다른 함수를 호출하기 직전에 스택에 푸시 한 함수 인수를 제거하기 위해 ESP를 증가시킬 수 있습니다. 이 시점에서 스택은 호출 된 함수를 호출하기 전과 동일한 상태로 돌아갑니다.


15

당신은 옳습니다. 스택 포인터는 스택의 맨 위 항목을 가리키고 기본 포인터 는 함수가 호출되기 전에 스택"이전"맨을 가리 킵니다 .

함수를 호출하면 로컬 변수가 스택에 저장되고 스택 포인터가 증가합니다. 함수에서 돌아 오면 스택의 모든 로컬 변수가 범위를 벗어납니다. 스택 포인터를 기본 포인터 (함수 호출 이전의 "이전"상단)로 다시 설정하면됩니다.

메모리 할당을하는이 방법은 매우 , 매우 빠르고 효율적입니다.


14
@Robert : 함수가 호출되기 전에 스택의 "이전"상단을 말하면 함수를 호출하기 직전에 스택으로 푸시되는 매개 변수와 호출자 EIP를 모두 무시합니다. 이것은 독자들을 혼란스럽게 할 수 있습니다. 표준 스택 프레임에서 EBP 는 함수에 들어간 직후 ESP가 가리키는 곳과 동일한 위치를 가리 킵니다 .
wigy

7

편집 : 자세한 설명 은 WikiBook에서 x86 어셈블리에 대한 x86 분해 / 기능 및 스택 프레임 을 참조하십시오 . Visual Studio 사용에 관심이있는 정보를 추가하려고합니다.

호출자 EBP를 첫 번째 로컬 변수로 저장하는 것을 표준 스택 프레임이라고하며 Windows의 거의 모든 호출 규칙에 사용할 수 있습니다. 호출자 또는 수신자가 전달 된 매개 변수를 할당 해제하는지와 레지스터에서 전달 된 매개 변수를 지정하는지에 따라 차이가 있지만, 표준 스택 프레임 문제와 직교합니다.

Windows 프로그램에 대해 말하면 Visual Studio를 사용하여 C ++ 코드를 컴파일 할 수 있습니다. Microsoft는 프레임 포인터 생략이라는 최적화를 사용하므로 실행 파일에 dbghlp 라이브러리와 PDB 파일을 사용하지 않고 스택을 걷는 것이 거의 불가능합니다.

이 프레임 포인터 생략은 컴파일러가 이전 EBP를 표준 위치에 저장하지 않고 다른 용도로 EBP 레지스터를 사용한다는 것을 의미하므로 지정된 함수에 로컬 변수가 필요한 공간을 모르면 호출자 EIP를 찾는 데 어려움을 겪습니다. 물론 Microsoft는이 경우에도 스택 워크를 수행 할 수있는 API를 제공하지만 일부 사용 사례에서는 PDB 파일에서 기호 테이블 데이터베이스를 조회하는 데 시간이 너무 오래 걸립니다.

컴파일 단위에서 FPO를 피하려면 / O2 사용을 피하거나 프로젝트의 C ++ 컴파일 플래그에 / Oy-를 명시 적으로 추가해야합니다. 릴리스 구성에서 FPO를 사용하는 C 또는 C ++ 런타임에 연결되어 있으므로 dbghlp.dll없이 스택 워크를 수행하기가 어렵습니다.


스택에 EIP가 저장되는 방법을 얻지 못했습니다. 레지스터가 아니어야합니까? 레지스터는 어떻게 스택에있을 수 있습니까? 감사!
삼키는 elysium

호출자 EIP는 CALL 명령 자체에 의해 스택으로 푸시됩니다. RET 명령어는 스택의 상단을 가져 와서 EIP에 넣습니다. 버퍼 오버런이있는 경우이 사실은 특권 스레드에서 사용자 코드로 이동하는 데 사용될 수 있습니다.
wigy

@devouredelysium EIP 레지스터 의 내용 (또는 value )은 레지스터 자체가 아니라 스택에 놓이거나 복사됩니다.
BarbaraKwarc 2012 년

@BarbaraKwarc value- able 입력에 감사드립니다 . 내 답변에서 OP가 누락 된 것을 볼 수 없었습니다. 실제로 레지스터는 원래 위치에 유지되며 해당 값만 CPU에서 RAM으로 전송됩니다. amd64 모드에서는 조금 더 복잡해 지지만 다른 질문으로 남겨 두십시오.
wigy

그 amd64는 어떻습니까? 궁금해.
BarbaraKwarc

6

우선 x86 스택은 높은 주소 값에서 낮은 주소 값으로 빌드되므로 스택 포인터는 스택의 맨 아래를 가리 킵니다. 스택 포인터는 다음 번 푸시 호출 (또는 호출)이 다음 값을 배치 할 지점입니다. 작업은 C / C ++ 문과 같습니다.

 // push eax
 --*esp = eax
 // pop eax
 eax = *esp++;

 // a function call, in this case, the caller must clean up the function parameters
 move eax,some value
 push eax
 call some address  // this pushes the next value of the instruction pointer onto the
                    // stack and changes the instruction pointer to "some address"
 add esp,4 // remove eax from the stack

 // a function
 push ebp // save the old stack frame
 move ebp, esp
 ... // do stuff
 pop ebp  // restore the old stack frame
 ret

기본 포인터는 현재 프레임의 상단입니다. ebp는 일반적으로 반송 주소를 가리 킵니다. ebp + 4는 함수의 첫 번째 매개 변수 (또는 클래스 메소드의이 값)를 가리 킵니다. ebp-4는 함수의 첫 번째 로컬 변수, 일반적으로 ebp의 이전 값을 가리 키므로 이전 프레임 포인터를 복원 할 수 있습니다.


2
ESP는 스택 의 맨 아래 를 가리 키지 않습니다 . 메모리 주소 지정 체계는 아무 관련이 없습니다. 스택이 낮은 주소 또는 높은 주소로 커지는 지 여부는 중요하지 않습니다. 스택의 "상단"은 항상 다음 값이 푸시되는 위치 (스택의 상단에 놓임) 또는 다른 아키텍처에서 마지막으로 푸시 된 값이 입력 된 위치 및 현재 위치입니다. 따라서 ESP는 항상 스택 을 가리 킵니다 .
BarbaraKwarc 2012 년

1
바닥 또는 염기 를 Where 스택 반면에, 인 (또는 오래된 ) 값이 넣어 다음 최근 값에 의해 덮여있다. 그것이 바로 EBP의 "기본 포인터"라는 이름에서 유래 한 것입니다. 서브 루틴의 현재 로컬 스택의 기본 (또는 하단)을 가리켜 야합니다.
BarbaraKwarc 2012 년

Barbara, Intel x86에서 스택은 UPSIDE DOWN입니다. 스택의 맨 위에는 스택으로 밀린 첫 번째 항목이 포함되고 그 다음에 각 항목이 맨 아래 항목으로 푸시됩니다. 스택의 맨 아래에는 새 항목이 배치됩니다. 프로그램은 1k부터 메모리에 배치되고 무한대로 커집니다. 스택은 무한대에서 시작하여 사실적으로 최대 mem 마이너스 ROM을 뺀 다음 0으로 증가합니다. ESP는 푸시 된 첫 번째 주소보다 값이 작은 주소를 가리 킵니다.
jmucchiello

1

어셈블리 프로그래밍을 한 지 오래되었지만 이 링크 가 유용 할 수 있습니다 ...

프로세서에는 데이터를 저장하는 데 사용되는 레지스터 모음이 있습니다. 이 중 일부는 직접 값이고 다른 일부는 RAM 내의 영역을 가리 킵니다. 레지스터는 특정 특정 작업에 사용되는 경향이 있으며 어셈블리의 모든 피연산자는 특정 레지스터에 특정 양의 데이터가 필요합니다.

스택 포인터는 주로 다른 프로 시저를 호출 할 때 사용됩니다. 최신 컴파일러를 사용하면 스택에 많은 데이터가 먼저 덤프되고 반환 주소가 따라 오므로 시스템은 반환하라는 메시지가 표시되면 반환 위치를 알 수 있습니다. 스택 포인터는 새 데이터를 스택으로 푸시 할 수있는 다음 위치를 가리키며 다시 튀어 나올 때까지 유지됩니다.

기본 레지스터 또는 세그먼트 레지스터는 방대한 양의 데이터의 주소 공간을 가리 킵니다. 두 번째 레지스터와 결합 된 Base 포인터는 메모리를 큰 블록으로 나누고 두 번째 레지스터는이 블록 내의 항목을 가리 킵니다. 이를위한 기본 포인터는 데이터 블록의 기본을 가리킨다.

어셈블리는 CPU마다 매우 다르다는 것을 명심하십시오. 내가 링크 한 페이지는 다양한 유형의 CPU에 대한 정보를 제공합니다.


세그먼트 레지스터는 x86에서 분리되어 있습니다. gs, cs, ss입니다. 메모리 관리 소프트웨어를 작성하지 않는 한 절대 만지지 마십시오.
Michael

ds는 또한 세그먼트 레지스터이며 MS-DOS 및 16 비트 코드 시대에는 64KB 이상의 RAM을 가리킬 수 없으므로 때때로 세그먼트 레지스터를 변경해야했습니다. 그러나 DOS는 20 비트 주소 포인터를 사용했기 때문에 최대 1MB의 메모리에 액세스 할 수있었습니다. 나중에 우리는 32 비트 시스템을 얻었으며, 일부는 36 비트 주소 레지스터와 64 비트 레지스터가 있습니다. 따라서 요즘에는 이러한 세그먼트 레지스터를 더 이상 변경할 필요가 없습니다.
Wim ten Brink

어떤 현대적인 OS는 386 개 세그먼트 사용하지 않습니다
아나 베츠

@ 폴 : 잘못된! 잘못된! 잘못된! 16 비트 세그먼트는 32 비트 세그먼트로 대체됩니다. 보호 모드에서 이는 메모리 가상화를 허용하여 기본적으로 프로세서가 물리적 주소를 논리적 주소로 매핑 할 수있게합니다. 그러나 응용 프로그램 내에서 OS가 메모리를 가상화했기 때문에 여전히 문제가 없습니다. 커널은 보호 모드에서 작동하므로 응용 프로그램을 플랫 메모리 모델로 실행할 수 있습니다. 참조 en.wikipedia.org/wiki/Protected_mode
빔 열 직전

@Workshop ALex : 그것은 기술입니다. 모든 최신 OS는 모든 세그먼트를 [0, FFFFFFFF]로 설정합니다. 그것은 실제로 계산되지 않습니다. 그리고 링크 된 페이지를 읽으면 모든 멋진 것들이 페이지로 완성 된 것을 볼 수 있습니다. 페이지는 훨씬 세분화되어 있습니다.
MSalters

-4

편집하다 네, 이것은 대부분 잘못입니다. 누구나 관심이있는 경우 완전히 다른 것을 설명합니다 :)

예, 스택 포인터는 스택의 맨 위를 가리 킵니다 (첫 번째 빈 스택 위치인지 확실하지 않은 마지막 전체 위치인지 여부). 기본 포인터는 실행중인 명령의 메모리 위치를 가리 킵니다. 이것은 컴퓨터에서 얻을 수있는 가장 기본적인 명령 인 opcode 수준입니다. 각 opcode 및 해당 매개 변수는 메모리 위치에 저장됩니다. 하나의 C 또는 C ++ 또는 C # 라인은 하나의 opcode로 변환 될 수 있으며, 복잡한 정도에 따라 둘 이상의 시퀀스로 변환 될 수 있습니다. 이것들은 프로그램 메모리에 순차적으로 쓰여지고 실행됩니다. 정상적인 상황에서는 기본 포인터가 한 명령 씩 증가합니다. 프로그램 제어 (GOTO, IF 등)의 경우 여러 번 증분하거나 다음 메모리 주소로 교체 할 수 있습니다.

이와 관련하여 기능은 특정 주소의 프로그램 메모리에 저장됩니다. 함수가 호출되면 특정 정보가 스택에서 푸시되어 프로그램이 함수가 호출 된 위치와 함수의 매개 변수로 돌아 왔음을 알 수 있습니다. 그러면 프로그램 메모리의 함수 주소가 기본 포인터. 다음 클럭 사이클에서 컴퓨터는 해당 메모리 주소에서 명령을 실행하기 시작합니다. 그런 다음 어느 시점에서 함수를 호출 한 명령 후에 메모리 위치로 돌아가서 거기서 계속합니다.


ebp가 무엇인지 이해하는 데 약간의 어려움이 있습니다. MASM 코드가 10 줄인 경우 해당 줄을 계속 실행하면 ebp가 항상 증가한다는 의미입니까?
삼키는 엘리시움

1
@Devoured-아니요. 사실이 아닙니다. eip가 증가 할 것입니다.
Michael

내가 말한 것은 옳지 만 EBP는 아니지만 IEP에게는 그렇습니까?
삼켜 elysium

2
예. EIP는 명령어 포인터이며 각 명령어가 실행 된 후 암시 적으로 수정됩니다.
Michael

2
내 나쁜. 다른 포인터를 생각하고 있습니다. 나는 내 뇌를 씻어 낼 것 같아요.
Stephen Friederichs

-8

esp는 "확장 스택 포인터"를 나타냅니다. ..... ebp는 "Something Base Pointer"를 나타냅니다 ..... eip는 "Something Instruction Pointer"를 나타냅니다 .... 스택 포인터는 스택 세그먼트의 오프셋 주소를 가리 킵니다. . 기본 포인터는 추가 세그먼트의 오프셋 주소를 가리 킵니다. 명령어 포인터는 코드 세그먼트의 오프셋 주소를 가리 킵니다. 이제 세그먼트에 대해 ... 프로세서 메모리 영역의 작은 64KB 분할입니다.이 프로세스를 메모리 분할이라고합니다. 이 게시물이 도움이 되었기를 바랍니다.


3
이것은 오래된 질문이지만 sp는 스택 포인터, bp는 기본 포인터, ip는 명령 포인터를 나타냅니다. 모든 사람의 시작 부분에있는 e는 32 비트 포인터라고 말합니다.
Hyden

1
여기서는 세분화와 관련이 없습니다.
BarbaraKwarc 2012 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.