NASM 어셈블리 언어

데이터 세그먼트

4.2.5) 데이터 세그먼트

4.2.5.1) 데이터 세그먼트의 레이블

데이터 세그먼트도 레이블(label)을 갖는다그런데 이 경우 레이블이라는 이름보다는 라벨이라고 읽는 편이 이해하기 수월할 수 있다왜냐하면 데이터 세그먼트의 레이블이 실제 메모리에 붙어서 해당 메모리를 가리키는 라벨의 역할을 하기 때문이다(사실 코드 세그먼트의 레이블도 코드의 주소 값에 대한 라벨로 보는 편이 옳다). 즉 라벨 그 자체는 가리키는 메모리의 주소 값을 나타낸다코드 세그먼트와 다른 점은데이터 세그먼트의 데이터에 라벨을 붙일 때는 콜론(:)을 사용하지 않는다는 점이다예를 들어 값이 1인 바이트에 lbl이라는 별명즉 라벨을 붙이려면 다음과 같이 한다.

lbl db 1

이제 데이터 세그먼트에서 데이터를 정의하는 방법을 알아보자앞으로 ‘lbl 라벨이 붙은 데이터 세그먼트의 메모리를 단순히 lbl이라고 하겠다.

 

4.2.5.2) 데이터를 정의하고 사용하기

데이터 정의문(data definition statement)은 메모리에 데이터를 정의할 때 사용한다구문은 다음과 같다.

[labeldirective initializer[, initializer]...

이는 말로 설명하는 것보다 예제를 보는 것이 이해가 빠르다다음은 “PC 어셈블리어” 문서에 소개된 예제를 발췌하여 정리한 것이다바로 위의 절과 함께 설명하겠다.

dataseg.asm

%include 'handy/handy.inc'

 

데이터 세그먼트의 시작을 알리는 segment .data 지시어입니다.

segment .data

L1 db 0 ; l1의 바이트 값을 0으로 설정

L2 dw 1000 ; l2의 워드 값을 1000으로 설정

L3 db 110101b ; l3의 바이트 값을 110101_2로 설정

L4 db 17o ; l4의 바이트 값을 17_8로 설정

L5 dd 1A92h ; l5의 더블워드 값을 1A92_16으로 설정

L6 resb 1 ; l6을 1바이트 메모리로 정의하고 초기화하지 않음

L7 db "A" ; l7의 바이트 값을 문자 A의 ASCII 값으로 설정

L8 db 0, 1, 2, 3 ; 4바이트를 정의

L9 db "w", "o", "r", "d", 0 ; C 문자열 "word"를 정의

L10 db 'word', 0 ; l10과 같음

L11 times 100 db 0 ; 100개의 db 0을 나열한 것과 같다

 

...

NASM에선 큰따옴표와 작은따옴표는 서로 같은 것으로 취급된다데이터가 연속적으로 정의되면 그 데이터들은 메모리에 연속해서 존재하게 된다따라서 L2는 L1의 바로 다음 메모리에 위치하게 된다배열을 정의하려면 L11과 같이 times 지시어를 이용한다.

4.2.5.1절에서 라벨이 메모리의 주소 값이라고 말했다. C에서는 주소 값을 이용해 해당 주소가 가리키는 값을 연산자를 이용해 획득할 수 있었다. NASM에서는 대신 대괄호(‘[’, ‘]’)를 이용한다그 방법은 다음과 같다.

dataseg.asm

...

 

코드 세그먼트의 시작을 알리는 segment .text 지시어입니다.

segment .text

global _main

_main:

push ebp

mov ebp, esp

 

mov al, [L1] ; al에 L1에 위치한 데이터를 대입한다

mov eax, L1 ; eax = L1에 위치한 바이트의 주소

mov [L1], ah ; L1에 위치한 바이트에 ah를 대입한다

mov eax, [L6] ; L6에 위치한 더블워드를 eax에 대입한다

add eax, [L6] ; eax = eax + L6에 위치한 더블워드

add [L6], eax ; L6에 위치한 더블워드 += eax

mov al, [L6] ; L6에 위치한 더블워드의 하위 비트를

; al에 대입한다

 

mov eax, 0

mov esp, ebp

pop ebp

ret

NASM의 중요한 특징이 이 예제에서 드러난다바로 어셈블러가라벨이 어떠한 데이터를 가리키고 있는지 전혀 신경을 쓰지 않는다는 점이다이는 컴파일러가 컴파일 시에 자료형을 검사하는 것과는 대조적이다후에는 데이터의 주소 값을 레지스터에 저장하고 C의 포인터 연산을 하듯 코드를 작성하게 되는데 이때도 포인터가 정확하게 사용되는지를 어셈블러가 검사하지 않는다이 때문에 어셈블리 프로그래밍은 C언어를 이용한 프로그래밍보다도 오류가 잦아지게 된다.

 

4.2.5.3) data와 bss

다음과 같은 코드를 생각하자.

char *str_ptr = "Hello, world!";

char str_arr[] = "Hello, world!";

int main(void) {

char *p = &str_arr[0]; // &str_ptr[0];

p[0] = 'h'; // p가 가리키는 메모리의 첫 번째 바이트를 'h'로 변경합니다.

return 0;

}

이 코드는 정상적으로 실행되는 코드다. p가 str_arr을 가리키고 있을 때는 첫 번째 문자는 잘 변경된다그렇다면p가 str_ptr을 가리키도록 예제를 수정해보자이때는 컴파일 시에는 오류가 발생하지 않지만 실행 시에 오류가 발생한다무엇이 문제인가?

결론부터 말하자면데이터 세그먼트도 수정 가능한 데이터 세그먼트와 수정 불가능한 데이터 세그먼트가 별도로 존재한다기본적으로 C의 전역 변수는 수정 가능한 데이터 세그먼트에 들어간다. const와 같은 한정자를 걸어놓지 않는 한 우리가 마음대로 수정할 수 있으니 당연한 것이다따라서 str_ptr 포인터 변수와 str_arr 배열 변수 모두 수정 가능한 데이터 세그먼트에 자리하게 된다.

문제는 str_ptr가 가리키는 “Hello, world!" 문자열은 수정 불가능한 데이터 세그먼트에 자리한다는 점이다. str_arr의 경우는 위와 같이 초기화를 진행하면 str_arr 배열을 위한 별도의 공간을 수정 가능한 데이터 세그먼트에 생성하고 문자열을 복사하므로 당연히 수정이 가능하다하지만 str_ptr이 포인터 변수는 수정 불가능한 메모리에 자리한 문자열의 주소를 가리키고 있으니위와 같이 값을 수정하려고 하면 오류가 발생하는 것이다.

바로 여기서 data와 bss의 차이를 확인할 수 있다수정 가능한 데이터가 들어가는 세그먼트는 bss 세그먼트다. data 세그먼트에는 수정 불가능한 데이터가 들어간다즉 이 경우 str_ptr, str_arr는 모두 bss 세그먼트게 저장되고, str_ptr이 가리키는 문자열 "Hello, world!"는 data 세그먼트에 들어간다따라서 우리가 컴파일러를 개발할 때는 전역 변수와 정적 변수는 모두 bss 세그먼트에 넣어야 한다.

이와 같이 데이터 세그먼트에 대해 알 수 있었다우리는 CIL을 이미 배웠으므로이 정도만 이해하면 나머지는jscc를 개발하면서 찾아보면 된다.

댓글

댓글 본문
graphittie 자세히 보기