Programming/Assembly

어셈블리어 - 변수 선언/활용하기

lee308812 2021. 3. 13. 13:40

[ 프로세스 메모리 구조 ]

- 변수를 선언하고 사용하기 위해서는 변수로 사용될 메모리 공간이 필요한데, 용도에 따라(초기화 유무, 지역변수/정적 또는 전역) 어느 영역에 할당하여 사용할지 결정해야 한다.

 

[ 변수 선언하기 ]

- 초기화 된 데이터 정적/전역변수를 메모리에 할당하기 위해서는 data 영역에 크기와 함께 지정하여 사용한다. 

- 초기화 되지 않은 정적/전역변수를 메모리에 할당하기 위해서는 bss 영역에 크기 및 개수와 함께 지정하여 사용한다. 

%include "io64.inc"

section .text
global CMAIN
CMAIN:
    mov rbp, rsp; for correct debugging
    ;write your code here

    
    xor rax, rax
    ret
    
section .data  ; 초기화된 정적/전역변수         
    a db 0x12               ; 1byte (byte) 변수 a 정의
    b dw 0x1234             ; 2byte (word) 변수 b 정의
    c dd 0x12345678         ; 4byte (dword) 변수 c 정의
    d dq 0x1234567812345678 ; 8byte (qword) 변수 d 정
    

section .bss    ; 초기화 되지 않은 정적/전역변수 
    e resb 10              ; 1byte (byte) 10개짜리 변수 e 정의
    f resw 5               ; 2byte (word) 5개짜리 변수 f 정의
    g resd 3               ; 4byte (dword) 3개짜리 변수 g 정의
    h resq 2               ; 8byte (qword) 2개짜리 변수 h 정의

 

- SASM 툴에서 위 코드를 입력하고 F5를 이용해 Debug를 수행하면 첫줄에서 멈추게 되는데,

이 때, [Debug] - [Show memory]를 체크해주면 현재 메모리 상태를 볼 수 있다.

아래와 같이 변수 이름을 입력하면 해당하는 변수 값을 볼 수 있다.

- a ~ d는 초기화된 변수이므로 지정해준 값으로 들어가 있으며, e~h는 초기화 되지 않은 변수이므로 모두 0으로 발라져있는 것을 알 수 있다.

 

- 그런데, 여기서 a 변수의 크기는 1byte이지만, Array Size를 15로 지정해 옆의 영역도 함께 보면 a,b,c,d가 모두 연속된 영역에 할당되었음을 알 수 있다.

- 그런데 여기서 보면 숫자는 얼추 맞는 것 같은데, 숫자의 순서가 조금 이상하다. 그 이유는 Intel/AMD CPU에서는 리틀 엔디안(Little Endian) 방식을 사용하고 있기 때문이며, 엔디안 방식이란 어떤 값을 메모리에 저장할 때 어떤식으로 저장할지에 대한 방법을 의미한다.

- 각 방법의 장단점은 아래와 같다.

- 어떤 2개의 값을 비교할 때 가장 큰 자리수부터 비교하는데, Big Endian의 경우 순서대로 비교하면 되므로 간단하다.

- 형변환을 할 때, 0x12345678에서 하위 1word만 가져오고자 할 경우, Little Endian의 경우 그냥 아래쪽 1word를 가져오면 끝이므로 간단하다.

 

- 또한, 변수에 여러개 값을 연속해서 넣는것도 가능하다.

 

[ 변수 활용하기 ]

- mov 명령어를 통해 변수 a의 값을 레지스터 A에 가져오기 위해서는 아래와 같이 하면 될 것 같다.

mov rax, a     ; 과연 a의 값을 A레지스터의 8byte 영역으로 가져올까??? 

- 그러나 A 레지스터의 8 byte 영역(RAX)을 확인해보면 이상한 값이 들어있는데, 이는 메모리에 저장된 변수를 지정할 때 그냥 이름을 써주면 주소값을 의미하기 때문이다.

- 진짜로 0x403010이 a의 주소가 맞는지를 확인하기 위해 0x403010 주소에 들어있는 값을 확인해보면, a값이 들어있다.

- 원래 의도대로 a에 들어있는 값을 가져오기 위해서는 []을 사용해야 한다.

mov al, [a]; 주소 a부터 시작해 1byte만큼 A레지스터의 1byte 영역으로 가져온다. 

rax의 상위 7바이트는 초기화되지 않아 쓰레기 값이 들어있지만, 하위 1byte에는 변수 a의 값(0x12)을 잘 가져왔다.

 

- 이번에는 변수 a의 값에 0x55를 넣으려고 아래와 같이 쓰면 Error가 발생한다.

mov [a], 0x55	; 에러 발생

 

크기가 지정되지 않았기 때문이다. 0x55가 0x0000055인지 0x0000000055인지 알수 없기 때문이며, 그동안 register에 상수를 넣을 때는 rax, eax등 크기를 지정해줬으나 상수를 변수에 넣고자 할 경우에는 반드시 크기를 지정해 줘야한다.

mov [a], byte 0x55		
mov [a], word 0x5555	; a는 1byte로 지정해줬었으므로, 옆의 영역을 침범해서 2byte(1word)가 저장된다. 
mov [b], ecx		; 대상이 레지스터면 이미 크기 정보(ecx)가 들어있으므로 OK

 

- 문자열을 사용하는 예제는 아래와 같다.

%include "io64.inc"

section .text
global CMAIN
CMAIN:
    mov rbp, rsp; for correct debugging
    ; write your code here
    PRINT_STRING helloworld ; 어셈블리어는 아니고 툴에서 문자열 출력을 위해 지원하는 매크로
    
    xor rax, rax
    ret

section .data
    helloworld db 'Hello World', 0x00 ; 문자열의 끝을 나타내기 위해 반드시 NULL(0x00)로 끝나야 한다.