32비트에서는 Hello, World 구문을 아래와 같이 띄울 수 있습니다.
/* 32bit version / GNU Assembly */ .data message: .string "Hello, World!\n" .text .global _main .extern _printf _main: # 스택 프레임 만들기 pushl %ebp movl %esp, %ebp # 함수의 본문 # printf(message); pushl $message xorb %al, %al call _printf addl $4, %esp # 스택 프레임 제거 movl %ebp, %esp popl %ebp ret
잠시 xorb %al, %al을 보자면… _printf 함수를 호출하기 전에 AL 레지스터의 값을 설정하는데, System V에서는 가변인수를 받는 함수를 호출할 때 AL 레지스터를 통해 매개변수가 기억되어있는 벡터 레지스터(부동소수점 실수를 기억하는 레지스터로서 xmm0 ~ xmm7)의 수를 지정합니다. 예를 들어 printf("%d", 0);, printf("%d%d", 0, 1);과 같이 매개변수로 정수가 전달된다면 벡터 레지스터를 사용하지 않으므로 AL = 0이 됩니다. 그러나 printf("%f", 0.0f), printf("%f%lf", 1.0f, 1.0);의 경우 매개변수로 부동소수점이 전달되는데 내부적으로는 이를 위해 벡터 레지스터를 사용하므로 AL = 1, AL = 2가 지정됩니다.
본론으로 돌아와 64비트 코드로 그대로 옮긴다면 아래와 같이 생각할 수 있습니다. 오퍼랜드의 크기를 나타내는 접미사 l을 q로 고치고 sp 레지스터의 가감을 4바이트에서 8바이트로 수정하면 될 것 같습니다. 그러나 Mac OS X 10.8에서 이 코드를 실행하면 컴파일 타임에 "32-bit absolute addressing is not supported in 64-bit mode"라는 오류가 뜹니다.
/* 64bit version / GNU Assembly */ .data message: .string "Hello, World!\n" .text .global _main .extern _printf _main: # 스택 프레임 만들기 pushq %rbp movq %rsp, %rbp # 함수의 본문 # printf(message); pushq $message xorb %al, %al call _printf addq $8, %rsp movq %rbp, %rsp popq %rbp ret
맥 OS 실행파일인 Mach-O에서는 "$message"와 같이 메모리 주소를 직접 지정하는 방식을 지원하지 않습니다. 대신 message(%rip) 또는 message@GOTPCREL(%rip)와 같이 전역 오프셋 테이블(global offset table, GOT)와 IP 레지스터의 값을 참조하여 간접적으로 메모리 주소를 지정합니다. 구체적인 내용은 https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/x86_64_code.html를 참고하시기 바랍니다.
/* 64bit version / GNU Assembly */ .data message: .string "Hello, World!\n" .text .global _main .extern _printf _main: # 스택 프레임 만들기 pushq %rbp movq %rsp, %rbp # 함수의 본문 # printf(message); pushq message@GOTPCREL(%rip) xorb %al, %al call _printf addq $8, %rsp # 스택 프레임 제거 movq %rbp, %rsp popq %rbp ret
컴파일 타임에는 오류가 없었지만 런타임 오류가 뜹니다. EXC_BAD_ACCESS라는 잘못된 메모리 주소 오류인데요, Mach-O 64비트 어셈블러부터는 처음 6개의 매개변수에 대해 스택이 아닌 레지스터를 통해 전달받습니다. 그 순서는 RDI, RSI, RDX, RCX, R8 및 R9입니다. 아래와 같이 수정하면 printf를 호출해 문자열을 출력할 수 있습니다.
/* 64bit version / GNU Assembly */ .data message: .string "Hello, World!\n" .text .global _main .extern _printf _main: # 스택 프레임 만들기 pushq %rbp movq %rsp, %rbp # 함수의 본문 # printf(message); movq message@GOTPRECL(%rip), %rdi xorb %al, %al call _printf # 스택 프레임 제거 movq %rbp, %rsp popq %rbp ret
이번엔 리눅스 실행 파일인 ELF 포맷으로 어셈블해보겠습니다. Mach-O와 달리 메모리 주소의 직접 지정이 가능합니다. 함수의 매개변수가 스택이 아닌 레지스터를 통해 전달된다는 것은 동일합니다.
/* 64-bit ELF */ .data message: .string "Hello, World!\n" .text .global main .extern printf main: pushq %rbp movq %rsp, %rbp movq $message, %rdi callq printf movq %rbp, %rsp popq %rbp movq $0, %rax retq
참고 사이트
- http://stackoverflow.com/questions/26394359/mach-o-64-bit-format-does-not-support-32-bit-absolute-addresses-nasm
- http://blog.loudhush.ro/2010/11/hello-world-in-64bit-mac-os-x.html
'Programming Language > Assembly' 카테고리의 다른 글
호출 규약(calling convention) 정리 (0) | 2014.08.15 |
---|---|
Visual Studio 2013에서 어셈블리어 코딩 후 실행하기 (12) | 2014.06.09 |
C-Style의 문자열 출력 (0) | 2013.12.03 |
산술 비교 알고리즘 - 3편. "<"와 ">=" (0) | 2013.04.12 |
산술 비교 알고리즘 - 2편. ">"와 "<=" (0) | 2013.04.12 |