어셈블러의 포맷에 대해서 알아보겠습니다.
어셈블러가 하는 일은 소스코드를 컴파일하여
CPU가 해석가능한 기계어 코드를 만드는 것인데
이것도 일종의 프로그램이므로 어셈블러마다
각각 포맷이 있습니다. 예를 들어 MASM과
NASM은 다른 포맷입니다. 같은 CPU를
사용해도 운영체제가 다르면 또 프로그램이
달라지니까 어셈블러는 하드웨어와
플랫폼에 의존적이라고 말할 수 있습니다.
요즘은 C언어에도 호환성이 있는 시대인데
java와 같은 write once run anywhere가 모토인
크로스 플랫폼과는 전혀 관계가 없기 때문에
이런 것들을 배우는게 시간낭비고 도움이
안된다는 아이디어도 충분히 설득력이 있습니다
다만 어셈블리어를 배우는 이유는
어셈블리어로 뭔가 쎄끈한 프로그램을
만들겠다는 것보다는 java나 C#같은
고수준 언어와 운영체제의 시스템을
더 잘 이해하기 위한 부분과 또는
성능에 최적화된 모듈을 직접 개발해야 할때
유용하게 사용할 수 있습니다.
(C언어는 어셈블리어 코드를 가져올 수 있다.
C확장을 사용하면 파이썬에서도 어셈블리어
코드를 실행하는 C언어를 사용할 수 있다)
우선 section 혹은 segment에 따라
무슨 차이가 있는지 알아보겠습니다.
아래 코드에서 보면 section .data는
초기화된 데이터가 들어갑니다.
section .bss 는 초기화되지 않은 데이터입니다.
section .text 는 명령어(코드)가 들어갑니다.
이 세개가 기본 section 입니다.
segment라는 키워드도 같은 의미로 사용합니다.
section .data
;for initialized data
;data should be initialized (of course it should be)
welcomeMSG: db "Hello NASM sections!",10,0
len: equ $-welcomeMSG ;constant
section .bss
;for uninitialized data
someValue: resq 1 ;3 element byte array
section .text
global _start
_start:
nop ;just a placeholder
mov rax, 1
mov rdi, 1
mov rsi, welcomeMSG
mov rdx, len
syscall
mov dword [someValue], dword 123
_exit:
mov rax, 60
mov rdi, 0
syscall
컴파일한 Makefile 입니다
all:
nasm -g -F dwarf -f elf64 main.asm -o main.o
ld main.o -o main
./main
readelf 를 사용해서 보면 section header에
.data .bss .text 가 있습니다.
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 64 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 18
Section header string table index: 4
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .data PROGBITS 0000000000000000 000004c0
0000000000000016 0000000000000000 WA 0 0 4
[ 2] .bss NOBITS 0000000000000000 000004e0
0000000000000008 0000000000000000 WA 0 0 4
[ 3] .text PROGBITS 0000000000000000 000004e0
0000000000000033 0000000000000000 AX 0 0 16
[ 4] .shstrtab STRTAB 0000000000000000 00000520
00000000000000ca 0000000000000000 0 0 1
[ 5] .symtab SYMTAB 0000000000000000 000005f0
0000000000000138 0000000000000018 6 12 8
[ 6] .strtab STRTAB 0000000000000000 00000730
0000000000000030 0000000000000000 0 0 1
[ 7] .rela.text RELA 0000000000000000 00000760
0000000000000030 0000000000000018 5 3 8
[ 8] .debug_aranges PROGBITS 0000000000000000 00000790
0000000000000030 0000000000000000 0 0 1
[ 9] .rela.debug_arang RELA 0000000000000000 000007c0
0000000000000030 0000000000000018 5 8 1
[10] .debug_pubnames PROGBITS 0000000000000000 000007f0
0000000000000012 0000000000000000 0 0 1
[11] .debug_info PROGBITS 0000000000000000 00000810
000000000000004a 0000000000000000 0 0 1
[12] .rela.debug_info RELA 0000000000000000 00000860
0000000000000078 0000000000000018 5 11 1
[13] .debug_abbrev PROGBITS 0000000000000000 000008e0
000000000000001b 0000000000000000 0 0 1
[14] .debug_line PROGBITS 0000000000000000 00000900
0000000000000045 0000000000000000 0 0 1
[15] .rela.debug_line RELA 0000000000000000 00000950
0000000000000018 0000000000000018 5 14 1
[16] .debug_frame PROGBITS 0000000000000000 00000970
0000000000000004 0000000000000000 0 0 8
[17] .debug_loc PROGBITS 0000000000000000 00000980
0000000000000010 0000000000000000 0 0 1
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.asm
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 SECTION LOCAL DEFAULT 3
5: 0000000000000000 1 OBJECT LOCAL DEFAULT 1 welcomeMSG
6: 0000000000000016 0 NOTYPE LOCAL DEFAULT ABS len
7: 0000000000000000 1 OBJECT LOCAL DEFAULT 2 someValue
8: 0000000000000027 0 NOTYPE LOCAL DEFAULT 3 _exit
9: 0000000000000000 0 SECTION LOCAL DEFAULT 11
10: 0000000000000000 0 SECTION LOCAL DEFAULT 13
11: 0000000000000000 0 SECTION LOCAL DEFAULT 14
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 3 _start
No version information found in this file.
주석은 ; 다음에 나오는 텍스트입니다.
명령어 다음에 나와되 됩니다.
;주석입니다.
mov rax, 1 ;명령어 다음에 주석입니다
이렇게 쓰면 됩니다.
소스코드 상에서 16진수는 0x 접두어를 붙입니다.
mov rax, 60 이면 mov rax, 0x3c 처럼 바꿀 수 있습니다.
8진수는 q를 접미어로 붙입니다.
예를 들어 8은 10q 입니다.
기본은 물론 10진수입니다.
데이타 섹션은 초기화된 데이타가 들어갑니다.
여기에 초기화된 데이터를 선언합니다.
여기에는 고수준 언어처럼 char, int 이런 것은
없지만 바이트 수로 따집니다.
db - define byte, 8 bit
dw - define word, 16 bit
dd - define double word, 32 bit
dq - define quad word, 64 bit
아래 예제는 .data 섹션에서 변수를
선언하는 예제입니다.
여러개의 요소를 할당하는 배열도
선언하고 초기화 할 수 있습니다.
부동소수점도 선언할 수 있습니다.
section .data
;for initialized data
;data should be initialized (of course it should be)
byteVar db 10 ;byte - gdb: cast char
charVar db "A" ;character
strVar db "This is string" ;string
wordVar dw 30000 ;16-bit - gdb: cast short
doubleVar dd 1000000000 ;32-bit - gdb: cast unsigned int
quadVar dq 5000000000 ;64-bit - gdb: cast unsigned long
wordArray dw 15,30,35 ;3 element array
floatVar dd 1.234567 ;float - gdb: cast float
section .bss
segment .text
global _start
_start:
mov byte [byteVar], byte 100
mov word [wordVar], word 65535
mov dword [doubleVar], dword 2500000000
mov al, byte [byteVar]
mov ax, word [wordVar]
mov eax, dword [doubleVar]
mov rax, qword [quadVar]
_exit:
mov rax, 0x3c
mov rdi, 0x0
syscall
text 섹션에서 사용하는 방법은
조금 차이가 있는데 레지스터는
al, ax, eax, rax 만 봐도 비트수가
확인이 되지만 변수는 몇 비트인지
그 이름만 가지고는 알 수 없습니다.
따라서 비트수를 지정해주는데
그 주소에서 비트만큼 접근해서
사용하기 때문입니다. 이것을
맞추지 않으면 대개는 컴파일러
에러나 워닝 메시지를 볼 수 있습니다.
여기에는 C언어 처럼 char, int 같은
기본 데이터형이 있는게 아닙니다.
바이트수를 기준으로 맞추는 거라서
좀 tricky 합니다. 이거를 매번 출력하면서
확인하는 것은 어려우니까 gdb 디버거를
좀 활용하는게 도움이 됩니다.
어셈블리어 GDB 디버거 사용법(기본) - NASM x86_64 어셈블리어 7