C언어에서 다루었던 포인터연산을 통한 변수 값 참조와 다차원 배열의 특정 위치의 값을 참조하는 방식을 어셈블리어를 통해 깊게 알아보자
int val[5]; 라는 정수형 배열이 있다고 생각해보자
주소값 | x | x + 4 | x + 8 | x + 12 | x + 16 |
값 | 1 | 5 | 2 | 1 | 3 |
배열의 메모리 값까지 생각 한다면 위와 같이 구성 되어있을 것이다
x = 배열의 시작주소
예제 1)
Reference | Type | Value |
val[4] | int | 3 |
val | int * | x |
val + 1 | int * | x + 4 |
&val[2] | int* | x + 8 |
val[5] | int | - |
*(val+1) | int | 5 |
C to Assembly
1.
int pnu[5] = {1, 5, 2, 1, 3};
int get_digit(int z[], int id){
return z[id];
}
위 코드는 매우 기본적인 배열의 특정 인덱스 값을 반환해주는 함수가 있다.
그럼 get_digit 함수는 어셈블리어로 어떻게 표현될까?
// %rdi = z
// %rsi = id
movl (%rdi, %rsi, 4), %eax
// base index scale
어셈블리어에서 return 되는 인자는 %rax 레지스터에 입력된다 (%eax는 %rax(8바이트) 의 하위 4바이트를 나타내는 레지스터)
%rdi 가 함수의 인자로 받은 z배열의 시작 주소를 가지고 있다
%rsi는 함수의 인자로 받은 인덱스(id)를 가지고 있다
movl 명령을 해석해보면
%eax = %rdi + %rsi * 4 이 되고
시작주소에서 intType의 크기 * %rsi(인덱스) 만큼 떨어진 곳의 값을 반환하게된다
2.
int pnu[5] = {1, 5, 2, 1, 3};
void zincr(int z[]){
for(int i = 0; i < 5; i++){
z[i]++;
}
}
pnu 배열의 값을 1씩 더해주는 함수이다
어셈블리어로 변환 해보자
// %rdi = z
// %rax = i
movl $0, %eax // i에 0을 넣어준다
jmp .L3 // L3로 점프
.L4: // 배열에 1씩 더해주는 동작
addl $1, (%rdi, %rax, 4) // %rdi + %rax * 4를 통해 z배열의 값에 접근하고 1을 더해준다
addq $1, %rax // i 에 1을 더해준다
.L3: // for문의 조건 확인
cmpq $4, %rax // 4와 i를 비교 (i - 4 를 통해 누가 더 큰지 비교)
jbe .L4 // 만약 i가 4보다 작거나 같으면 L4로 점프
rep; ret // 아니라면 종료
// for(int i = 0; i < 5; i++)에서 i 는 5와 비교하는데 왜 i-4와 비교해서 작거나 같은지 확인하는 방법을 쓰는가?
// 그냥 어셈블리어의 최적화 방식이라고 하심
# 잠깐 개념정리 #
int *A[3] ?
*와 [3]은 연산자 우선순위에 따라서 [3]을 먼저 적용한다
따라서 int *A[3] == int *(A[3]) 이라고 볼 수 있다
그래서 int *(A[3])은 길이가 3인 포인터 값을 가지는 배열이다
= 배열의 각 원소에는 특정 원소를 가리키는 포인터 값이 들어있다
int (*A) [3] ?
이 자체로만 본다면 A는 배열이 아니다
A에는 길이가 3인 int형 배열의 시작주소를 가리키는 포인터 값이 들어있다
3.
이제 2차원 배열로 가보자
int pnu[4][5] = { {1, 5, 2, 0, 6},
{1, 5, 2, 1, 3},
{1, 5, 2, 1, 7},
{1, 5, 2, 2, 1}};
int *get_pnu_zip(int id){
return pnu[id];
}
위 함수는 pnu 배열의 id번째 행의 시작 주소값을 반환하는 함수이다
// %rdi = id
leaq (%rdi, %rdi, 4), %rax // 열의 개수가 5 이므로 (id * 5) == (%rdi + %rdi*4) 값을 %rax에 넣는다
leaq (, %rax, 4), %rax // int형의 크기가 4바이트 이므로 %rax에 4를 곱해주면 주소값이 나온다
4.
int pnu[4][5] = { {1, 5, 2, 0, 6},
{1, 5, 2, 1, 3},
{1, 5, 2, 1, 7},
{1, 5, 2, 2, 1}};
int get_pnu_digit(int index, int dig){
return pnu[index][dig];
}
위 함수는 pnu배열의 index번째 행, dig번째 열의 값을 반환하는 함수이다
// %rdi = index(행의 위치)
// %rsi = dig(열의 위치)
leaq (%rdi, %rdi, 4), $rax // index를 현재 행의 시작위치로 만들어준다. index*5
addl %rax, %rsi // index 와 현재 열의 위치를 더한다
movl pnu(, %rsi, 4), %eax // pnu(pnu의 시작 주소값) + 4*행의 위치+열의 위치 (int형이 4바이트이므로) 의 값을 리턴
'CS > SystemSoftware' 카테고리의 다른 글
SystemSoftware - Buffer OverFlow #1 (0) | 2021.05.19 |
---|---|
Bomb Lab Solution Phase_1 ~ secret_phase (1) | 2021.05.10 |
SystemSoftware - Machine data(Assembly) #2 (0) | 2021.04.30 |
SystemSoftware - float #2 (0) | 2021.04.20 |
SystemSoftware - float #1 (1) | 2021.04.20 |