ex1 LLVM

以下のCのSource codewがコンパイラによってどう変換されるかを調べる

    extern int printf(char *,...);
    #define TYPE int
    TYPE f(TYPE a, TYPE b) {
        return a + b;
    }
    int main() 
    {
        TYPE a = 1;
        TYPE b = 2;
        printf("%x = %x + %x \n",f(a,b),a,b);
        return 0;
    }

cpp

  • clang -E での出力を調べる

  • 変換されている部分はどこか。 printf を protoptype ではなく #include で定義した時はどうなるか。

解説

clangの -E オプションはmanによると -E Run the preprocessor stage.

つまりプリプロセッサまで処理を行う。 普通のコンパイルの時はあくまで内部的に行われている。

(参考:プリプロセッサ)

Report on Compiler consturction Lecture Exercise 1.1

clangのEオプションはプリプロセッサの展開結果のみを表示するオプションである。 まずはexternしている状態の出力を見てみる

 $clang -E report.c
# 1 "report.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 330 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "report.c" 2
extern int printf(char *,...);

int f(int a, int b) {
    return a + b;
}
int main()
{
    int a = 1;
    int b = 2;
    printf("%x = %x + %x \n",f(a,b),a,b);
    return 0;
}

最初の#から始まる数行はコンパイルエラーとソースコードを対応付ける為の出力である。 プリプロセッサではコメントの除去や単語の置換などを行っており 一切に#includeが除去,関数fが単純なint型に戻されている。 つまりCompilerにとって解釈しやすい形のCコードと変換されている。

次に#include で定義した場合の状態を見てみる。

 # 1 "report.c"
 # 1 "<built-in>" 1
 # 1 "<built-in>" 3
 # 330 "<built-in>" 3
 # 1 "<command line>" 1
 # 1 "<built-in>" 2
 # 1 "report.c" 2
 # 1 "/usr/include/stdio.h" 1 3 4
 # 64 "/usr/include/stdio.h" 3 4
 # 1 "/usr/include/sys/cdefs.h" 1 3 4
 # 587 "/usr/include/sys/cdefs.h" 3 4
 # 1 "/usr/include/sys/_symbol_aliasing.h" 1 3 4
 # 588 "/usr/include/sys/cdefs.h" 2 3 4
 # 653 "/usr/include/sys/cdefs.h" 3 4
 # 1 "/usr/include/sys/_posix_availability.h" 1 3 4
 # 654 "/usr/include/sys/cdefs.h" 2 3 4
 # 65 "/usr/include/stdio.h" 2 3 4
 # 1 "/usr/include/Availability.h" 1 3 4
 # 190 "/usr/include/Availability.h" 3 4
 # 1 "/usr/include/AvailabilityInternal.h" 1 3 4
 # 191 "/usr/include/Availability.h" 2 3 4
 # 66 "/usr/include/stdio.h" 2 3 4

 # 1 "/usr/include/_types.h" 1 3 4
 # 27 "/usr/include/_types.h" 3 4
 # 1 "/usr/include/sys/_types.h" 1 3 4
 # 33 "/usr/include/sys/_types.h" 3 4
 # 1 "/usr/include/machine/_types.h" 1 3 4
 # 32 "/usr/include/machine/_types.h" 3 4
 # 1 "/usr/include/i386/_types.h" 1 3 4
 # 37 "/usr/include/i386/_types.h" 3 4
 typedef signed char __int8_t;



 typedef unsigned char __uint8_t;
 typedef short __int16_t;
 typedef unsigned short __uint16_t;
 typedef int __int32_t;
 typedef unsigned int __uint32_t;
 typedef long long __int64_t;
 typedef unsigned long long __uint64_t;

 typedef long __darwin_intptr_t;
 typedef unsigned int __darwin_natural_t;
 # 70 "/usr/include/i386/_types.h" 3 4
 typedef int __darwin_ct_rune_t;





 typedef union {
  char __mbstate8[128];
  long long _mbstateL;
 } __mbstate_t;

 typedef __mbstate_t __darwin_mbstate_t;


 typedef long int __darwin_ptrdiff_t;







 typedef long unsigned int __darwin_size_t;


 ** 中略**

 }
 # 379 "/usr/include/stdio.h" 3 4
 void flockfile(FILE *);
 int ftrylockfile(FILE *);
 void funlockfile(FILE *);
 int getc_unlocked(FILE *);
 int getchar_unlocked(void);
 int putc_unlocked(int, FILE *);
 int putchar_unlocked(int);



 int getw(FILE *);
 int putw(int, FILE *);


 __attribute__((__availability__(swift, unavailable, message="Use mkstemp(3) instead.")))

 __attribute__((deprecated("This function is provided for compatibility reasons only.  Due to security concerns inherent in the design of tempnam(3), it is highly recommended that you use mkstemp(3) instead.")))

 char *tempnam(const char *__dir, const char *__prefix) __asm("_" "tempnam" );
 # 417 "/usr/include/stdio.h" 3 4
 # 1 "/usr/include/sys/_types/_off_t.h" 1 3 4
 # 30 "/usr/include/sys/_types/_off_t.h" 3 4
 typedef __darwin_off_t off_t;
 # 418 "/usr/include/stdio.h" 2 3 4


 int fseeko(FILE * __stream, off_t __offset, int __whence);
 off_t ftello(FILE * __stream);





 int snprintf(char * restrict __str, size_t __size, const char * restrict __format, ...) __attribute__((__format__ (__printf__, 3, 4)));
 int vfscanf(FILE * restrict __stream, const char * restrict __format, va_list) __attribute__((__format__ (__scanf__, 2, 0)));
 int vscanf(const char * restrict __format, va_list) __attribute__((__format__ (__scanf__, 1, 0)));
 int vsnprintf(char * restrict __str, size_t __size, const char * restrict __format, va_list) __attribute__((__format__ (__printf__, 3, 0)));
 int vsscanf(const char * restrict __str, const char * restrict __format, va_list) __attribute__((__format__ (__scanf__, 2, 0)));
 # 442 "/usr/include/stdio.h" 3 4
 # 1 "/usr/include/sys/_types/_ssize_t.h" 1 3 4
 # 30 "/usr/include/sys/_types/_ssize_t.h" 3 4
 typedef __darwin_ssize_t ssize_t;
 # 443 "/usr/include/stdio.h" 2 3 4


 int dprintf(int, const char * restrict, ...) __attribute__((__format__ (__printf__, 2, 3))) __attribute__((availability(macosx,introduced=10.7)));
 int vdprintf(int, const char * restrict, va_list) __attribute__((__format__ (__printf__, 2, 0))) __attribute__((availability(macosx,introduced=10.7)));
 ssize_t getdelim(char ** restrict __linep, size_t * restrict __linecapp, int __delimiter, FILE * restrict __stream) __attribute__((availability(macosx,introduced=10.7)));
 ssize_t getline(char ** restrict __linep, size_t * restrict __linecapp, FILE * restrict __stream) __attribute__((availability(macosx,introduced=10.7)));
 # 458 "/usr/include/stdio.h" 3 4
 extern const int sys_nerr;
 extern const char *const sys_errlist[];

 int asprintf(char ** restrict, const char * restrict, ...) __attribute__((__format__ (__printf__, 2, 3)));
 char *ctermid_r(char *);
 char *fgetln(FILE *, size_t *);
 const char *fmtcheck(const char *, const char *);
 int fpurge(FILE *);
 void setbuffer(FILE *, char *, int);
 int setlinebuf(FILE *);
 int vasprintf(char ** restrict, const char * restrict, va_list) __attribute__((__format__ (__printf__, 2, 0)));
 FILE *zopen(const char *, const char *, int);





 FILE *funopen(const void *,
                  int (* _Nullable)(void *, char *, int),
                  int (* _Nullable)(void *, const char *, int),
                  fpos_t (* _Nullable)(void *, fpos_t, int),
                  int (* _Nullable)(void *));
 # 498 "/usr/include/stdio.h" 3 4
 # 1 "/usr/include/secure/_stdio.h" 1 3 4
 # 31 "/usr/include/secure/_stdio.h" 3 4
 # 1 "/usr/include/secure/_common.h" 1 3 4
 # 32 "/usr/include/secure/_stdio.h" 2 3 4
 # 42 "/usr/include/secure/_stdio.h" 3 4
 extern int __sprintf_chk (char * restrict, int, size_t,
      const char * restrict, ...);
 # 52 "/usr/include/secure/_stdio.h" 3 4
 extern int __snprintf_chk (char * restrict, size_t, int, size_t,
       const char * restrict, ...);







 extern int __vsprintf_chk (char * restrict, int, size_t,
       const char * restrict, va_list);







 extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
        const char * restrict, va_list);
 # 499 "/usr/include/stdio.h" 2 3 4
 # 2 "report.c" 2

 int f(int a, int b) {
     return a + b;
 }
 int main()
 {
     int a = 1;
     int b = 2;
     printf("%x = %x + %x \n",f(a,b),a,b);
     return 0;
 }

展開されたコード自体は同様であるが,#includeするとヘッダーファイルをプリプロセッサ時点で全て読み込み展開する。 その為externしている状態と比較するとstdio.h内の今回のコードとは直接関係ない関数や構造体の宣言も行われている。


アセンブラ

clang -S -O0 で出力されるアセンブラについて調べる

O0なので最適化されていない状態のアセンブラが吐かれる。

 $clang -S -O0 report.c
 $cat report.s | pbcopy

>

     .section    __TEXT,__text,regular,pure_instructions
     .macosx_version_min 10, 12
     .globl    _f
     .align    4, 0x90
 _f:                                     ## @f
     .cfi_startproc
 ## BB#0:
     pushq    %rbp
 Ltmp0:
     .cfi_def_cfa_offset 16
 Ltmp1:
     .cfi_offset %rbp, -16
     movq    %rsp, %rbp
 Ltmp2:
     .cfi_def_cfa_register %rbp
     movl    %edi, -4(%rbp)
     movl    %esi, -8(%rbp)
     movl    -4(%rbp), %esi
     addl    -8(%rbp), %esi
     movl    %esi, %eax
     popq    %rbp
     retq
     .cfi_endproc

     .globl    _main
     .align    4, 0x90
 _main:                                  ## @main
     .cfi_startproc
 ## BB#0:
     pushq    %rbp
 Ltmp3:
     .cfi_def_cfa_offset 16
 Ltmp4:
     .cfi_offset %rbp, -16
     movq    %rsp, %rbp
 Ltmp5:
     .cfi_def_cfa_register %rbp
     subq    $16, %rsp
     movl    $0, -4(%rbp)
     movl    $1, -8(%rbp)
     movl    $2, -12(%rbp)
     movl    -8(%rbp), %edi
     movl    -12(%rbp), %esi
     callq    _f
     leaq    L_.str(%rip), %rdi
     movl    -8(%rbp), %edx
     movl    -12(%rbp), %ecx
     movl    %eax, %esi
     movb    $0, %al
     callq    _printf
     xorl    %ecx, %ecx
     movl    %eax, -16(%rbp)         ## 4-byte Spill
     movl    %ecx, %eax
     addq    $16, %rsp
     popq    %rbp
     retq
     .cfi_endproc

     .section    __TEXT,__cstring,cstring_literals
 L_.str:                                 ## @.str
     .asciz    "%x = %x + %x \n"


 .subsections_via_symbols

main関数から関数fを読んでいる箇所は callq _print の部分である。 今回は関数fに(1,2)を引数として与えている為に

    movl    $1, -8(%rbp)
    movl    $2, -12(%rbp)
    movl    -8(%rbp), %edi
    movl    -12(%rbp), %esi

これらの部分でそれぞれ1,2を%edi,%esiに設定している。

関数fはアセンブラ結果の5行目から宣言されている。 実際に関数fが計算している箇所を確認すると

     movl    %edi, -4(%rbp)
     movl    %esi, -8(%rbp)
     movl    -4(%rbp), %esi
     addl    -8(%rbp), %esi
     movl    %esi, %eax
     popq    %rbp

と先程設定した%ediと%esiから%rbpにそれぞれ値を格納した後 %esiに第1引数を戻し,%esiに直接addlした後にpopしている事が分かる。 またaddl命令なのでこれらは32bitで演算されている。

次に最適化した状態のアセンブラを確認する

$ clang -S -O report.c
$ cat report.s
     .section    __TEXT,__text,regular,pure_instructions
     .macosx_version_min 10, 12
     .globl    _f
     .align    4, 0x90
 _f:                                     ## @f
     .cfi_startproc
 ## BB#0:
     pushq    %rbp
 Ltmp0:
     .cfi_def_cfa_offset 16
 Ltmp1:
     .cfi_offset %rbp, -16
     movq    %rsp, %rbp
 Ltmp2:
     .cfi_def_cfa_register %rbp
     leal    (%rdi,%rsi), %eax
     popq    %rbp
     retq
     .cfi_endproc

     .globl    _main
     .align    4, 0x90
 _main:                                  ## @main
     .cfi_startproc
 ## BB#0:
     pushq    %rbp
 Ltmp3:
     .cfi_def_cfa_offset 16
 Ltmp4:
     .cfi_offset %rbp, -16
     movq    %rsp, %rbp
 Ltmp5:
     .cfi_def_cfa_register %rbp
     leaq    L_.str(%rip), %rdi
     movl    $3, %esi
     movl    $1, %edx
     movl    $2, %ecx
     xorl    %eax, %eax
     callq    _printf
     xorl    %eax, %eax
     popq    %rbp
     retq
     .cfi_endproc

     .section    __TEXT,__cstring,cstring_literals
 L_.str:                                 ## @.str
     .asciz    "%x = %x + %x \n"


 .subsections_via_symbols

最適化していない状態と見比べると関数fがもはや何もしなくなっている。 mainルーチンを確認すると,最初に出力用のフォーマットを宣言し 既に1+2の結果である3を%esiに格納しているのがわかる。

つまりコンパイラが最適化として関数fが2変数(1と2)をaddしているだけであり,出力さえすれば良いと解釈した為 1+2の結果である3を関数fを呼ばずに静的に設定し,出力をしているのが理解できる。 その為add命令が書かれておらず,callqでprintされるまではひたすらmov命令で数値をレジスタに格納しているのみである。


LLVM

$ clang -emit-llvm -S

LLVM バイトコードの出力が得られることを確認せよ。 アセンブラとの対応を示せ。

$ clang -emit-llvm -S report.c

上記のコマンドを実行するとreport.llというファイルが生成され,これがllvmの中間木ファイルである。

$ cat report.ll | pbcopy

> ; ModuleID = 'report.c'
> source_filename = "report.c"
> target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
> target triple = "x86_64-apple-macosx10.12.0"
> 
> @.str = private unnamed_addr constant [15 x i8] c"%x = %x + %x \0A\00", align 1
> 
> ; Function Attrs: nounwind ssp uwtable
> define i32 @f(i32, i32) #0 {
>   %3 = alloca i32, align 4
>   %4 = alloca i32, align 4
>   store i32 %0, i32* %3, align 4
>   store i32 %1, i32* %4, align 4
>   %5 = load i32, i32* %3, align 4
>   %6 = load i32, i32* %4, align 4
>   %7 = add nsw i32 %5, %6
>   ret i32 %7
> }
> 
> ; Function Attrs: nounwind ssp uwtable
> define i32 @main() #0 {
>   %1 = alloca i32, align 4
>   %2 = alloca i32, align 4
>   %3 = alloca i32, align 4
>   store i32 0, i32* %1, align 4
>   store i32 1, i32* %2, align 4
>   store i32 2, i32* %3, align 4
>   %4 = load i32, i32* %2, align 4
>   %5 = load i32, i32* %3, align 4
>   %6 = call i32 @f(i32 %4, i32 %5)
>   %7 = load i32, i32* %2, align 4
>   %8 = load i32, i32* %3, align 4
>   %9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i32 %6, i32 %7, i32 %8)
>   ret i32 0
> }
> 
> declare i32 @printf(i8*, ...) #1
> 
> attributes #0 = { nounwind ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
> attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
> 
> !llvm.module.flags = !{!0}
> !llvm.ident = !{!1}
> 
> !0 = !{i32 1, !"PIC Level", i32 2}
> !1 = !{!"Apple LLVM version 8.0.0 (clang-800.0.42.1)"}
>

今回のLLVMの中間表現を確認していく。まず1行目はコメントである。

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"

二行目はDataLayoutを定義しており,まずe-mで始まっていることからターゲットのデータレイアウトがリトルエンディアンであることを示している。 次のm:0の部分はMach-O形式でし出力するということを示している。以降はビットサイズの指定などを行っている。

target triple = "x86_64-apple-macosx10.12.0" この部分はtargetとなるアーキテクチャを記述している。

@.str = private unnamed_addr constant [15 x i8] c"%x = %x + %x \0A\00", align 1

@がついているものはllvmの中でグローバル変数として解釈されるが,これは後のprintfで書き出されるものである。 アセンブラの.section部分と対応している。

>     .section    __TEXT,__cstring,cstring_literals
> L_.str:                                 ## @.str
>     .asciz    "%x = %x + %x \n"

次のブロックでは関数fについて記述されている。 引数2つはそれぞれ%0,%1に保存されている。 この時点で%0,%1はおそらくmain関数で代入された値のレジスタそのものであるため 一旦別レジスタに格納し,それを再度loadすることで値の中身をコピーし使っている。

アセンブラではaddlなど命令の後にlが付くことで32bitであると読み取れたが llvmの場合i32とbit長で幾つかのものを指定しているという対応がある。

関数fの最後では

%7 = add nsw i32 %5, %6 とadd命令で%5と%6の値を足して%7レジスタに格納している。 addの後に続くnswはNo Signed Wrapであり,計算上オーバーフローなどが発生した場合は llvmのPoison Valueとして処理されるという意味である。

main関数ではallocateでスタックフレーム上に利用する値の場所を確保した後に store未例で一旦そのスタックに値を書き込み,load命令でレジスタに入れて演算している。

>   store i32 0, i32* %1, align 4
>   store i32 1, i32* %2, align 4
>   store i32 2, i32* %3, align 4
>   %4 = load i32, i32* %2, align 4
>   %5 = load i32, i32* %3, align 4

ここで%4,%5にはそれぞれ1,2が32bitで入っている.

%6 = call i32 @f(i32 %4, i32 %5)

この部分で返り値32bitの関数fが%4,%5を引数として呼び出され結果が%6に代入されている。

出力場所はこの部分であり,既に%4,%5は使っている為別のレジスタに値を入れている。

>   %9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i32 0, i32 0), i32 %6, i32 %7, i32 %8)

またprintf関数はライブラリ関数であるのでdeclare命令で関数宣言を行っているのみである。


llvmのバイトコードが最適化によってどう変化するかも確認する。

$ clang -emit-llvm -O -S report.c

; ModuleID = 'report.c'
source_filename = "report.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.12.0"

@.str = private unnamed_addr constant [15 x i8] c"%x = %x + %x \0A\00", align 1

; Function Attrs: norecurse nounwind readnone ssp uwtable
define i32 @f(i32, i32) #0 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}

; Function Attrs: nounwind ssp uwtable
define i32 @main() #1 {
  %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i64 0, i64 0), i32 3, i32 1, i32 2)
  ret i32 0
}

; Function Attrs: nounwind
declare i32 @printf(i8* nocapture readonly, ...) #2

attributes #0 = { norecurse nounwind readnone ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind ssp uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #2 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cx16,+fxsr,+mmx,+sse,+sse2,+sse3,+sse4.1,+ssse3" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"PIC Level", i32 2}
!1 = !{!"Apple LLVM version 8.0.0 (clang-800.0.42.1)"}

結果を見ると関数fは先程のアセンブラと同様にほぼ何もしない様に置き換わっている。 定義自体は2つのレジスタの中身をaddするとなっているが今回は呼び出されていない事が分かる。

ここでmain関数を確認するとわずか2行になっており

>  %1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i64 0, i64 0), i32 3, i32 1, i32 2)

この部分で既に計算された 3,1,2が先程先生した出力フォーマットに流れている事が分かる。

これはアセンブラの

>     leaq    L_.str(%rip), %rdi
>     movl    $3, %esi
>     movl    $1, %edx
>     movl    $2, %ecx
>     xorl    %eax, %eax
>     callq    _printf

この場所に対応している。 つまりllvmの中間木の時点で簡単な計算と使わなくても良い関数は,それぞれ最適化されて処理しているという事が分かる。


aout

出力される a.out を otool を使って調べる。

otoolはosXのバイナリ表示ツールである。 今回の出力したa.outを確認してみる。

otoolは幾つかのoptionがあるので,試していく。 まずLオプションで使用されている共有ライブラリのバージョンなどがわかる。

$ otool -L a.out                                                            

> a.out:
>         /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.60.2)

この結果このバイナリは/usr/lib/libSystem.B.dylibというCライブラリを使用している事が分かる。 dylib拡張子がついているものはMarch-O特有のものである。

March-OはHeader,Load Commands,Dataの3ブロックで構成されている。 さらに先頭にはFat Headerが付随しているh。headerを確認するにはhオプションを使う

$otool -h a.out

> Mach header
>       magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
>  0xfeedfacf 16777223          3  0x80           2    15       1200 0x00200085

またtオプションをつけるとTEXTsectionの部分のバイナリが出る。 これにvをつけると逆アセンブルも行われる。

otool -t a.out                                                                                                               (hg)-[default]
a.out:
Contents of (__TEXT,__text) section
0000000100000f10        55 48 89 e5 89 7d fc 89 75 f8 8b 75 fc 03 75 f8
0000000100000f20        89 f0 5d c3 66 66 66 2e 0f 1f 84 00 00 00 00 00
0000000100000f30        55 48 89 e5 48 83 ec 10 c7 45 fc 00 00 00 00 c7
0000000100000f40        45 f8 01 00 00 00 c7 45 f4 02 00 00 00 8b 7d f8
0000000100000f50        8b 75 f4 e8 b8 ff ff ff 48 8d 3d 3f 00 00 00 8b
0000000100000f60        55 f8 8b 4d f4 89 c6 b0 00 e8 0e 00 00 00 31 c9
0000000100000f70        89 45 f0 89 c8 48 83 c4 10 5d c3

$otool -tv a.out
> a.out:
> (__TEXT,__text) section
> _f:
> 0000000100000f10    pushq    %rbp
> 0000000100000f11    movq    %rsp, %rbp
> 0000000100000f14    movl    %edi, -0x4(%rbp)
> 0000000100000f17    movl    %esi, -0x8(%rbp)
> 0000000100000f1a    movl    -0x4(%rbp), %esi
> 0000000100000f1d    addl    -0x8(%rbp), %esi
> 0000000100000f20    movl    %esi, %eax
> 0000000100000f22    popq    %rbp
> 0000000100000f23    retq
> 0000000100000f24    nopw    %cs:(%rax,%rax)
> _main:
> 0000000100000f30    pushq    %rbp
> 0000000100000f31    movq    %rsp, %rbp
> 0000000100000f34    subq    $0x10, %rsp
> 0000000100000f38    movl    $0x0, -0x4(%rbp)
> 0000000100000f3f    movl    $0x1, -0x8(%rbp)
> 0000000100000f46    movl    $0x2, -0xc(%rbp)
> 0000000100000f4d    movl    -0x8(%rbp), %edi
> 0000000100000f50    movl    -0xc(%rbp), %esi
> 0000000100000f53    callq    0x100000f10
> 0000000100000f58    leaq    0x3f(%rip), %rdi
> 0000000100000f5f    movl    -0x8(%rbp), %edx
> 0000000100000f62    movl    -0xc(%rbp), %ecx
> 0000000100000f65    movl    %eax, %esi
> 0000000100000f67    movb    $0x0, %al
> 0000000100000f69    callq    0x100000f7c
> 0000000100000f6e    xorl    %ecx, %ecx
> 0000000100000f70    movl    %eax, -0x10(%rbp)
> 0000000100000f73    movl    %ecx, %eax
> 0000000100000f75    addq    $0x10, %rsp
> 0000000100000f79    popq    %rbp
> 0000000100000f7a    retq

書式にやや違いがあるが,ほぼアセンブラが再現できている。


(5) ARMアセンブラ

-arch arm を付けて、ARMのアセンブラの出力を調べよ

clangのarchオプションの後にアーキテクチャを指定するとそのアセンブラが出力される。

$ clang -S -O0 -arch arm report.c $ cat report.s

>     .section    __TEXT,__text,regular,pure_instructions
>     .macosx_version_min 10, 12
>     .syntax unified
>     .globl    _f
>     .align    2
> _f:                                     @ @f
> @ BB#0:
>     sub    sp, sp, #16
>     mov    r2, r1
>     mov    r3, r0
>     str    r0, [sp, #12]
>     str    r1, [sp, #8]
>     ldr    r0, [sp, #12]
>     add    r0, r0, r1
>     str    r2, [sp, #4]            @ 4-byte Spill
>     str    r3, [sp]                @ 4-byte Spill
>     add    sp, sp, #16
>     bx    lr
> 
>     .globl    _main
>     .align    2
> _main:                                  @ @main
> @ BB#0:
>     push    {r7, lr}
>     mov    r7, sp
>     sub    sp, sp, #24
>     mov    r0, #0
> k    str    r0, [r7, #-4]
>     mov    r1, #1
>     str    r1, [r7, #-8]
>     mov    r1, #2
>     str    r1, [sp, #12]
>     ldr    r2, [r7, #-8]
>     str    r0, [sp, #8]            @ 4-byte Spill
>     mov    r0, r2
>     bl    _f
>     ldr    r2, [r7, #-8]
>     ldr    r3, [sp, #12]
>     ldr    r1, LCPI1_0
> LPC1_0:
>     add    r1, pc, r1
>     str    r0, [sp, #4]            @ 4-byte Spill
>     mov    r0, r1
>     ldr    r1, [sp, #4]            @ 4-byte Reload
>     bl    _printf
>     ldr    r1, [sp, #8]            @ 4-byte Reload
>     str    r0, [sp]                @ 4-byte Spill
>     mov    r0, r1
>     mov    sp, r7
>     pop    {r7, lr}
>     bx    lr
>     .align    2
> @ BB#1:
>     .data_region
> LCPI1_0:
>     .long    L_.str-(LPC1_0+8)
>     .end_data_region
> 
>     .section    __TEXT,__cstring,cstring_literals
> L_.str:                                 @ @.str
>     .asciz    "%x = %x + %x \n"
> 
> 
> .subsections_via_symbols
>

ここで関数fを見てみる.

_f:                                     @ @f
@ BB#0:
    sub    sp, sp, #16
    mov    r2, r1
    mov    r3, r0
    str    r0, [sp, #12]
    str    r1, [sp, #8]
x86アセンブラの場合はmovをした後にaddlを行っていたが
armの場合はstr命令が走っている.
    ldr    r0, [sp, #12]
    add    r0, r0, r1
またadd命令などを見るとアドレッシングモードを利用している事がわかる。
    str    r2, [sp, #4]            @ 4-byte Spill
    str    r3, [sp]                @ 4-byte Spill
    add    sp, sp, #16
    bx    lr

@ 4-byte Spillのコメントを見るとarmの場合str命令で書くbyteごと 確保領域を変更することで使用するレジスタのビットサイズを操作している。

results matching ""

    No results matching ""