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

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가 그 순간에 어떻게 진화하는지 알고 싶다. (나는 이미 프롤로그가 어떻게 작동하는지 이해했다.



답변

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
  • 현지인을위한 공간 할당

답변

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

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

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

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

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


답변

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

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

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


답변

편집 : 자세한 설명 은 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없이 스택 워크를 수행하기가 어렵습니다.


답변

우선 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의 이전 값을 가리 키므로 이전 프레임 포인터를 복원 할 수 있습니다.


답변

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

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

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

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

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


답변

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

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

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