[시스템 프로그래밍] 프로시저
프로시저, 함수, 메서드.. 모두 같은 말이다.
그러니 혼용해서 사용해도 괜찮다.
함수 내부에서 다른 함수를 호출하는 방식의 코드를 접한 적이 있을 것이다.
C를 공부할 때 스택메모리가 활성화돼서 함수를 호출할 때 마다 스택의 윗부분에 올리는 방식으로 작동한다고 배웠는데, 어셈블리에서는 이 내용이 어떻게 구현되는지 살펴보자.
어셈블리에서 프로시저를 다룰 때 세 가지가 처리된다.
1. 제어의 전달
함수 내부에서 다른 함수를 호출할 때, 실행 중인 함수를 잠시 멈추고 호출한 함수를 실행해야 한다.
즉, ProgramCounter를 호출한 함수의 시작주소로 설정하고 함수가 리턴될 경우를 대비해 원래 실행하던 함수의 위치도 저장한다.
2. 데이터의 전달
함수를 호출할 때 인자로 정보를 전달하고, 함수가 리턴될 때 리턴값으로 정보를 전달한다.
3. 메모리 관리
함수가 리턴 될 때 함수 내부에서 만들어진 지역변수들은 모두 반납된다.
중요한 건, 위의 모든 것들이 스택 메모리를 사용해 메모리를 관리된다는 점이다.
스택은 주소가 감소하는 방향으로 성장함에 주의하자.
스택의 꼭대기는 %rsp 레지스터가 항상 가리키고 있고, %rsp 레지스터는 스택 공간의 가장 작은 주소를 저장한다.
함수 실행 도중 새로운 함수가 호출되면 스택의 꼭대기에 호출된 함수를 추가한다.
C에서 배운 바와 같이 이 메모리를 스택 프레임이라고 부른다.
최대한 기존 레지스터를 활용하다가, 기존 레지스터가 부족할 경우 스택 메모리를 활성화 하는 경우도 있다.
pushq Src
(x86-64 기준)
1. %rsp를 8 감소시킨다.
2. %rsp가 가리키는 주소에 피연산자를 기록한다.
먼저 빈 공간을 확보한 후 기록하는 작업을 수행한다.
popq Dest
1. %rsp가 가리키는 주소에서 값을 읽는다.
2. %rsp를 8 증가시킨다.
pop 명령어는 스택에서 값을 읽어오고 Dest에 작성하는 작업을 수행한다.
따라서 먼저 읽은 후, 스택 크기를 조절한다.
push와 pop은 스택에만 사용할 수 있는 명령어이다.
어셈블리 코드를 조사했을 때 뭔가 push와 pop 명령어가 보인다? 그럼 뭔가 새로운 함수가 호출됐음을 유추할 수 있다.
call / ret
어셈블리어에서 함수를 호출할 때는 call 명령어를, 함수를 반환할 때는 ret 명령어를 사용한다.
call label로 사용되는 call 명령어는 jmp 명령어와 유사하다.
<call>
1. 리턴 주소를 스택에 push 해준다.
2. label로 점프한다. (여기서 label은 나중에 링커가 주소로 변환한다)
여기서 리턴 주소는 call 바로 다음에 위치한 명령어의 주소를 의미한다. (리턴 주소는 실행하던 함수의 스택 프레임에 해당한다)
<ret>
1. call 명령어를 수행할 때 push 했던 리턴 주소를 pop 한다.
2. 얻어온 리턴 주소로 점프한다.
스택 프레임에는 리턴 주소와 Local Storage, 임시 공간 등이 포함된다.
스택에서 pop 되거나 push 될 때 마다 스택 프레임의 시작 부분을 가리키는 %rbp 레지스터와 스택 프레임의 끝을 가리키는 %rsp 레지스터를 조작해 스택 프레임을 관리한다.
함수를 재귀 호출하는 경우를 생각해보자.
함수를 실행하던 도중 함수를 호출하고, 그 함수가 호출된 후에는 원래 실행하던 작업을 계속해야 한다.
따라서 원래 작업하던 내용들이 저장돼야하는데, 이 내용들이 스택 프레임이 저장된다.
레지스터는 한정돼있다.
main 함수에서 레지스터를 모두 사용했다. main 함수에서 다른 함수를 호출하는데.. 호출하는 함수에서도 레지스터를 사용해야 한다.
이 때 main 함수에서 사용하는 레지스터의 정보를 스택 프레임에 저장하고, 호출하는 함수에서 레지스터를 다시 사용한다.
함수가 리턴됐을 때 스택 프레임에 저장한 정보를 다시 사용한다. (스택 프레임의 Saved Registers 부분 사용)
어떤 레지스터들이 언제 사용되는지는 컴파일러마다 다르지만..
대략적으로는 위와 같이 사용된다. (함수의 리턴 값은 %rax 레지스터에 저장된다.)
함수에서 인자를 전달할 때 사용되는 레지스터는 6개인데, 6개를 넘어서 인자를 전달하는 경우 스택 탑에 넣는 방법을 사용한다. (위의 그림에서 Optional 부분을 사용한다. Argument build area라고도 부름)
지역 변수를 저장할 레지스터가 부족하거나, 지역변수에 연산자 &를 사용하는 경우 메모리에 데이터를 저장해야 한다.
즉, 스택 프레임에 Local Variable 부분을 만들고 스택을 적절하게 조작해 데이터를 저장한다.
(전역변수의 경우 초기화된 경우에는 메모리의 .data 영역에 할당되고, 초기화되지 않은 경우는 메모리의 .bss 영역에 할당된다. 힙 메모리가 아니고, 따로 데이터 영역이 있음 https://13months.tistory.com/313 )
스택 메모리에는 프로그램에서 실행하는 함수들에 대한 기록이 모두 남아있다.
따라서 해커들은 공격 대상의 스택 메모리를 조사해 현재 어떤 프로그램을 실행하는지 알아내고, 리턴 값을 조작해 랜섬웨어를 심어놓는 등 다양한 방법으로 공격한다.
일단은 C언어가 어떻게 어셈블리어로 변환되는지를 공부하고 있지만, 다른 언어들도 비슷한 방향으로 번역되니 어셈블리어를 제대로 공부해두자.
'Computer Science > System Programming' 카테고리의 다른 글
[시스템 프로그래밍] 프로세스 (2) (1) | 2022.11.06 |
---|---|
[시스템 프로그래밍] 프로세스 (1) (0) | 2022.11.06 |
[시스템 프로그래밍] 어셈블리어 - 반복문 (0) | 2022.10.24 |
[시스템 프로그래밍] 어셈블리어 - 조건문 (0) | 2022.10.23 |
[시스템 프로그래밍] 어셈블리어 (0) | 2022.10.13 |
댓글
이 글 공유하기
다른 글
-
[시스템 프로그래밍] 프로세스 (2)
[시스템 프로그래밍] 프로세스 (2)
2022.11.06 -
[시스템 프로그래밍] 프로세스 (1)
[시스템 프로그래밍] 프로세스 (1)
2022.11.06 -
[시스템 프로그래밍] 어셈블리어 - 반복문
[시스템 프로그래밍] 어셈블리어 - 반복문
2022.10.24 -
[시스템 프로그래밍] 어셈블리어 - 조건문
[시스템 프로그래밍] 어셈블리어 - 조건문
2022.10.23