x86linux gasでhello world

ちょっとだけやってほとんど忘れてるgasの記憶確認としてHello Worldを書いてみました。

関数なし版

セクション、基本的なx86 asmの使い方、エントリポイント、システムコールwrite&exitを呼ぶ、アドレッシング

# puts hello world
# build: as hello.s -o hello.o && ld hello.o -o hello
# check: ./hello ; echo $? #=> 13 (output length)
        .section        .data           # static value section
message:
        .ascii  "Hello World!\n\0"

        .section        .text           # code section
        .equ    SYSCALL,        0x80
        .equ    EXIT,           1
        .equ    WRITE,          4
        .equ    STDOUT,         1
.globl  _start                          # global symbol: _start
_start:                                 # entry point
        movl    $message,       %ecx    # buf = message
        movl    $0,     %edx            # init len = 0
calc_len:
        cmpb    $0,     (%ecx, %edx)    # buf[len] == 0
        je      puts
        incl    %edx                    # len++
        jmp     calc_len
puts:
        movl    $STDOUT,        %ebx    # fd = stdout
        movl    $WRITE,         %eax    # syscall 4: write
        int     $SYSCALL                # write(fd, buf, len)
exit:
        movl    %eax,   %ebx            # val = write(...): length of output
        movl    $EXIT,  %eax            # syscall 1: exit
        int     $SYSCALL                # exit(val)

関数化版

C規約関数の呼び出し方、呼び出され方、スタックポインタ、ベースポインタ

# puts hello world
# build: as hello.s -o hello.o && ld hello.o -o hello
# check: ./hello ; echo $? #=> 13 (output length)
        .section        .data           # static value section
message:
        .ascii  "Hello World!\n\0"

        .section        .text           # code section
        .equ    SYSCALL,        0x80
        .equ    EXIT,           1
        .equ    WRITE,          4
        .equ    STDOUT,         1

.globl  exit
exit:
        pushl   %ebp
        movl    %esp,   %ebp
        movl    8(%ebp),        %ebx
        movl    $EXIT,  %eax
        int     $SYSCALL
        popl    %ebp
        ret


.globl  strlen
strlen:
        pushl   %ebp                    # enter func magic1
        movl    %esp,   %ebp            # enter func magic2

        movl    8(%ebp),        %edx    # 8(%ebp): first int32 param
        movl    $0,     %eax            # eax: set return value
.strlen_loop:
        cmpb    $0,     (%edx, %eax)
        je      .strlen_exit
        addl    $1,     %eax
        jmp     .strlen_loop
.strlen_exit:

        popl    %ebp                    # exit func magic1
        ret                             # exit func magic2


.globl  puts
puts:
        pushl   %ebp
        movl    %esp,   %ebp

        subl    $4,     %esp
        movl    8(%ebp), %eax           # eax = *(ebp+8)
        movl    %eax,   (%esp)          # *esp = eax
        call    strlen
        addl    $4,     %esp

        movl    %eax,           %edx    # eax: return val of strlen
        movl    8(%ebp),        %ecx
        movl    $STDOUT,        %ebx
        movl    $WRITE,         %eax
        int     $SYSCALL

        popl    %ebp
        ret


.globl  _start
_start:
        subl    $4,     %esp            # shift esp by args size
        movl    $message,       (%esp)  # set first arg
        call    puts                    # call func
        addl    $4,     %esp            # unshift esp

        subl    $4,     %esp
        movl    %eax,   (%esp)
        call    exit
        addl    $4,     %esp

付録: gccインラインアセンブラ

int strlen(char* str) {
  int len = 0;
  while (*str++ != '\0') len++;
  return len;
}

int puts(char* str) {
  int ret, len;
  len = strlen(str);
  __asm__("movl %2, %%edx;"
          "movl %1, %%ecx;"
          "movl $1, %%ebx;"
          "movl $4, %%eax;"
          "int $0x80;"
          "movl %%eax, %0;"
          :"=r" (ret)
          :"r" (str), "r"(len)
          :"%eax", "%ebx", "%ecx", "%edx");
  return ret;
}

void exit0(int v) {
  __asm__("movl $1, %%eax;"
          "int $0x80;"
          :
          :"b" (v)
          :"%eax");
  return;
}

int main() {
  exit0(puts("Hello World!\n"));
  return 0;
}

4つのレジスタを使うwriteでは、直接eaxなどに変数を割り当てできないっぽい("=a"(ret)とかくとコンパイルエラーが出る)。

あと、stdlibとかぶるexitという名前は使えませんでした。

cファイルからアセンブリコードに変換するには、

gcc -S hello.c -o hello.c.s