본문 바로가기

Programming Language/Assembly

[GAS/XCode]64비트 어셈블리어에서 메모리 번지 지정 오류 해결

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

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




참고 사이트