홈 > IT > IT정보
IT정보

스택, 어셈블리 간략 설명

스택, 어셈블리 설명


제한사항이 좀 있어 상세히 설명을 못했지만, 간단하게 훑어보는 정도로 보면 좋을 것 같습니다. 


1. Stack(스택)

    1-1. ESP, EBP

    1-2. RSP, RBP

2. Register

3. 실습


1. Stack(스택)

    - 스택은 아래에서 순차적으로 위로 쌓이는 특징을 가지고 있다. 즉 주소값의 높은 곳에서 낮은 곳으로 증가한다.

    - LIFO(Last-In-First-Out) 의 성질을 가지고 있으며, 자료를 스택에서 제거할때 스택의 마지막으로 입력된 자료가 제일 먼저 나오게 된다. 그래서 사용은 호출 또는 저장한 순서 반대로 사용이 된다.

    - 스택의 맨 아래, 주소값이 제일 높은 부분을 base 라 하고, 스택의 맨 위 주소값이 가장 낮은 곳을 top이라 부른다. 이때 자료는 top에 stack이 순차적으로 쌓인다.

    - 포인터는 Base와 Top을 가리키며, 이 포인터를 Base Pointer, Stack Pointer 라 하고, 그래서 BP, SP 라 부른다.

    1-1. ESP, EBP

        - 32bit 환경에서 쓰는 8개의 범용 레지스터에 포함된다.

        - 인텔 초기에는 16bit 였으며, 32bit를 사용하면서, 확장된 BP, SP라 불렀으며, 이것이 Extended BP, Extended SP 이다. 그래서 EBP, ESP 이다.

    1-2. RSP, RBP

        - 64bit 환경에서 쓰는 레지스터이다.

        - 64bit 환경에서 쓰는 16개의 레지스터에 포함된다.

2. Register(레지스터)

    - CPU의 요청을 처리하는데 필요한 데이터를 일시적으로 저장하는 기억장치를 말한다.

    - 데이터 저장이 순간적인지 영구적인지에 따라 주기억장치(RAM...), 보조기억장치(HDD...) 에 저장이 된다. 이 데이터들의 주소나 명령들을 저장하는 곳이 레지스터이다.

    - 32bit는 16bit, 64bit는 32bit 의 호환성을 고려하여 설계 되었다.

3. 실습

간단한 코드를 gdb로 disassemble 하여 위의 내용들을 하나씩 알아보고, 어셈블리어 간단한 명령어를 알아보자.

코드는 다음과 같다.

test.c

da44171706bfcc209e097f2e7b4ac5b9_1517144740_1068.PNG

<코드>

​    - 코드설명을 하면, 아래와 같다.

    - main 함수에서 int형 변수 선언 3개와 total 함수를 호출한다.

    - total 함수에서는 전달인자로 int형 변수 두개를 받고 함수 내에서는 int형 변수 선언과, 그 변수에 전달인자로 받은 두 개의 변수의 합을 저장하여 반환한다. 

    - 반환값을 바로 sumresult로 넘겨 정해진 문자열과 그 값을 출력하는 간단한 예제이다.

​<disassemble> 

da44171706bfcc209e097f2e7b4ac5b9_1517149215_3083.PNG 

    - 위 코드를 test라는 이름으로 컴파일하고, gdb를 통해 결과값을 얻고자 gdb를 실행시켰다.

da44171706bfcc209e097f2e7b4ac5b9_1517148577_349.PNG
    - intel로 disassemble 설정을 해주고 main 함수를 disassemble 하여 어셈블리 코드를 출력하였다.

    - 코드는 다음과 같다.

da44171706bfcc209e097f2e7b4ac5b9_1517148799_0768.PNG
​<설명>    

1.

da44171706bfcc209e097f2e7b4ac5b9_1517149891_1419.PNG 

    - 먼저 하나하나 살펴보면 맨위에 push rbp가 보인다. push는 stack에 자료를 집어넣는 명령어이다. 즉 rbp를 stack에 집어넣겠다는 의미이다.

    - 이때 rbp가 삽입될때 stack에 SFP(Save Frame Pointer)이 값으로 stack에 삽입되는데 SFP는 새로 함수를 호출할때, 스택을 새로 생성하기 전 rbp, rsp의 복귀주소를 저장해 둘 수 있는 공간을 의미한다.

    - 이어서 mov rbp,rsp 가 있는것을 확인할 수 있다. mov는 값을 집어넣을때 쓰는 명령어이다. 즉 mov A,B 라 하면 B에 있는 값을 A에 넣겠다는 의미이다. 따라서, rsp의 값을 rbp에 넣겠다는 의미이다. 즉 새로운 rbp 생성을 의미한다.

    - 위의 push rbp와 mov rbp,rsp로 스택프레임을 형성한다.

2.

da44171706bfcc209e097f2e7b4ac5b9_1517150001_5719.PNG
    ​- sub rsp,0x10 이 있는데, sub 명령어는 sub A,B 인경우 A에서 B를 빼는 것을 의미한다. 즉 rsp 값에서 0x10 만큼 빼는 것을 의미한다. 즉 rsp를 원래 있던 곳에서 0x10 만큼 떨어진 곳으로 이동시킨 것이다.

     - 왜 rsp에서 0x10 만큼 떨어진 곳으로 이동을 시킬까? 이유는 다음과 같다. int형 변수를 이어서 3개를 선언하는데 크기는 12byte이다. 그리고 rsp는 rbp와 같기 때문에 rbp의 크기인 4byte를 더 떨어뜨려 이동시킨다. 그렇게 해서 이어서 선언할 변수의 크기를 확보할수 있다. 그래서 총 16byte가 이동하므로, 0x10만큼 이동하게 된다. 그림으로 설명하면 아래와 같다.

c687c412fad3ed5bc2a97b49d978c40c_1517152073_5353.PNG
 

     - 이어서 앞에서 설명한 명령어 mov가 나온다. 그리고 명령 3줄을 다 봐보자. 

mov DWORD PTR [rbp-0xc],0x1 -> rbp 기준으로 0xc(12byte) 만큼 이동한 자리에 1 값을 넣는다.

mov DWORD PTR [rbp-0x8],0x2 -> rbp 기준으로 0x8(8byte) 만큼 이동한 자리에 2 값을 넣는다.

mov DWORD PTR [rbp-0x4],0x0 -> rbp 기준으로 0x4(4byte) 만큼 이동한 자리에 0 값을 넣는다.

여기서 좀만 자세히 들여다보면 이상한 점을 찾을 수 있다. 앞에서 분명 스택의 특징 중 높은 주소 값에서 낮은 주소 값으로 증가 한다고 하였다. 근데 rbp-0xc 주소값 자리에 1값을, rbp-0x8 주소값 자리에 2값을, rbp-0x4 주소값 자리에 0값을 넣는것을 살펴 볼 수 있다. 낮은 주소값 부터 값이 삽입 된 것이다. 이유는 리틀엔디안에 따라서 이렇게 저장이 된 것이다. 리틀엔디안과 빅엔디안은 아래 간단히 설명하였다.


※ 리틀엔디안과 빅엔디안 ※ 

리틀엔디안 : 낮은(하위)주소부터 낮은 바이트부터 기록한다.

    - 예를 들어 0x12345678 를 저장한다면 [하위주소 | 0x78 | 0x56 | 0x34 | 0x12 | 상위주소] 이런 순서로 저장이된다.

    - 산술연산에서 비교적 높은 속도를 내기 때문에 많이 쓴다.

빅엔디안 : 낮은(하위)주소부터 높은 바이트부터 기록한다.

    - 예를 들어 0x12345678 를 저장한다면 [하위주소 | 0x12 | 0x34 | 0x56 | 0x78 | 상위주소] 이런 순서로 저장이된다.

    - 사람이 보는 것과 동일하게 저장이 된다.

3.

f91fb049a2318be429b9619ff9924d9e_1517224160_2031.PNG

    - 다음 명령어는

mov edx,DWORD PTR [rbp-0x8]

mov eax DWORD PTR [rbp-0xc] 

이다. edx와 eax는 산술연산에 사용되는 레지스터이다. 코드 뒤에 두 값의 합을 구하기 때문에 edx와 eax에 각각 저장을 하는 것이다.

    - edx, eax 는 32bit에서 쓰는 "범용"레지스터라고 앞에서 설명을 하였다. 그래서 64bit 환경에서도 써도 무방한 레지스터이다.


4.

d420bdb149925f72b072af0b4803f7f9_1517232712_6253.PNG
    - total 함수를 호출하였다.

    - SFP(Save Frame Pointer)를 통해 새로 함수를 호출하면서, 스택을 새로 생성하기전 ebp, esp 주소를 저장해 둘 수 있다.

5. 

    ​- (total 함수 호출 이후 esp, ebp 와 함수 내 스택 구조 등의 내용은 b 라는 gdb 명령어를 사용해 break point 걸어 확인해 볼 수 있지만, 현재 제약된 상황(권한 제한 등)으로 설명을 올리기가 힘드므로 나중에 시간이 되면 수정하여 올리겠습니다.)

    - 확인해 보고 싶다면, total 함수에 break point 를 걸고 esp, ebp 내 메모리 출력 방식을 활용해 출력해 보면 될 것 같습니다.

6.

d420bdb149925f72b072af0b4803f7f9_1517234964_9568.PNG
    ​- total 함수, 즉 disas func2 를 실행해도 어셈블리 코드 마지막 부분에서 확인해 볼 수 있으며, 현 화면의 어셈블리 코드에서도 확인이 가능하다.

    ​- leave를 통해 stack을 정리하고, ret을 통해 함수의 반환을 하고, 나머지 명령어들을 실행시키고 프로그램을 종료시킨다.

    - 위 5번과 마찬가지로 leave 에 break point를 걸고 스택상황을 확인해 볼 수 있지만, 확인이 어려운 관계로 직접해보시는 것을 추천드립니다.


(참고)

스택 이해 : http://jihwan4862.tistory.com/1

레지스터 이해 : https://blog.naver.com/mjnms/220460806744

  • 페이스북으로 보내기
  • 트위터로 보내기
  • 구글플러스로 보내기
  • 카카오스토리로 보내기
  • 네이버밴드로 보내기
  • 네이버로 보내기
  • 텀블러로 보내기
  • 핀터레스트로 보내기
2 Comments
2 Selovic 18-02-11 14:31
C언어로 구현할 때 어셈블리로 코딩을 했으면 조금 더 알아보기 쉬웠을 수도 있을거같아요 ^^
2 realdragonhead 18-02-11 15:22
아 감사합니다! 생각하지 못햇네요 ㅠㅠ 한번 시도해보겠습니다! ^_^
Service
등록된 이벤트가 없습니다.
글이 없습니다.
글이 없습니다.
Comment
글이 없습니다.
Banner
등록된 배너가 없습니다.
hacure@hacure.com
9:30 ~ 17:30, 공휴일 휴무
런치타임 : 12:30 ~ 13:30

Bank Info

IBK기업은행 004-090413-01-015
예금주 주식회사하큐어소프트
Facebook Twitter GooglePlus KakaoStory NaverBand