x86/x86-64/ARM/AArch64/PowerPC/PowerPC64のアセンブリコードを読んでみる

簡単なC言語コードを各アーキテクチャ向けにコンパイルした結果のメモを兼ねて、アセンブリコードを読んでみる。

環境

Ubuntu 14.04.2 LTS 64bit版

$ uname -a
Linux vm-ubuntu64 3.13.0-48-generic #80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:        14.04
Codename:       trusty

$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4

$ arm-linux-gnueabi-gcc --version
arm-linux-gnueabi-gcc (Ubuntu/Linaro 4.7.3-12ubuntu1) 4.7.3

$ aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-gcc (Ubuntu/Linaro 4.8.2-13ubuntu1) 4.8.2 20140110 (prerelease) [ibm/gcc-4_8-branch merged from gcc-4_8-branch, revision 205847]

$ powerpc-linux-gnu-gcc --version
powerpc-linux-gnu-gcc (Ubuntu 4.8.2-16ubuntu3) 4.8.2

$ powerpc64le-linux-gnu-gcc --version
powerpc64le-linux-gnu-gcc (Ubuntu 4.8.2-16ubuntu4) 4.8.2

アーキテクチャ用のgccをインストールする

それぞれ対応するパッケージがあるのでインストールする。

$ sudo apt-get install gcc                        # x86-64
$ sudo apt-get install libc6-dev-i386             # x86
$ sudo apt-get install gcc-arm-linux-gnueabi      # ARM
$ sudo apt-get install gcc-aarch64-linux-gnu      # AArch64
$ sudo apt-get install gcc-powerpc-linux-gnu      # PowerPC
$ sudo apt-get install gcc-powerpc64le-linux-gnu  # PowerPC64

加えて、マルチアーキテクチャに対応したobjdumpコマンドを使うためにbinutils-multiarchをインストールする。

$ sudo apt-get install binutils-multiarch

C言語コード

/* hello.c */
#include <stdio.h>
#include <sys/mman.h>

void *call_mmap()
{
    void *p = mmap(NULL, 0x1000, 0x7, 0x22, -1, 0);
    return p;
}

int main(int argc, char *argv[])
{
    void *p;
    if (argc > 1) {
        p = call_mmap();
        printf("%p\n", p);
    }
    return 0;
}

main関数のif文で条件分岐、call_mmap関数で関数呼び出しの仕組みを確認する。

x86

x86-64用のgcc-m32オプションを付けることで、x86用の実行ファイルがコンパイルできる。 以降すべて、コンパイルは最適化オプション(-O1)をつけて行う。

$ gcc -m32 -O1 hello.c -o hello-x86
$ objdump -d hello-x86

main関数およびcall_mmap関数の内容を抜き出すと次のようになる。

0804846d <call_mmap>:
 804846d:       83 ec 2c                sub    esp,0x2c
 8048470:       c7 44 24 14 00 00 00    mov    DWORD PTR [esp+0x14],0x0
 8048477:       00
 8048478:       c7 44 24 10 ff ff ff    mov    DWORD PTR [esp+0x10],0xffffffff
 804847f:       ff
 8048480:       c7 44 24 0c 22 00 00    mov    DWORD PTR [esp+0xc],0x22
 8048487:       00
 8048488:       c7 44 24 08 07 00 00    mov    DWORD PTR [esp+0x8],0x7
 804848f:       00
 8048490:       c7 44 24 04 00 10 00    mov    DWORD PTR [esp+0x4],0x1000
 8048497:       00
 8048498:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 804849f:       e8 9c fe ff ff          call   8048340 <mmap@plt>
 80484a4:       83 c4 2c                add    esp,0x2c
 80484a7:       c3                      ret

080484a8 <main>:
 80484a8:       55                      push   ebp
 80484a9:       89 e5                   mov    ebp,esp
 80484ab:       83 e4 f0                and    esp,0xfffffff0
 80484ae:       83 ec 10                sub    esp,0x10
 80484b1:       83 7d 08 01             cmp    DWORD PTR [ebp+0x8],0x1
 80484b5:       7e 1d                   jle    80484d4 <main+0x2c>
 80484b7:       e8 b1 ff ff ff          call   804846d <call_mmap>
 80484bc:       89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 80484c0:       c7 44 24 04 70 85 04    mov    DWORD PTR [esp+0x4],0x8048570
 80484c7:       08
 80484c8:       c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
 80484cf:       e8 8c fe ff ff          call   8048360 <__printf_chk@plt>
 80484d4:       b8 00 00 00 00          mov    eax,0x0
 80484d9:       c9                      leave
 80484da:       c3                      ret
 80484db:       66 90                   xchg   ax,ax
 80484dd:       66 90                   xchg   ax,ax
 80484df:       90                      nop

条件分岐にはcmp命令とjle命令、関数の引数にはスタックを使い、戻り値はeaxレジスタに入ることがわかる。

x86-64

x86-64AMD64とも呼ばれる。

$ gcc -O1 hello.c -o hello-x86-64
$ objdump -d hello-x86-64
000000000040059d <call_mmap>:
  40059d:       48 83 ec 08             sub    rsp,0x8
  4005a1:       41 b9 00 00 00 00       mov    r9d,0x0
  4005a7:       41 b8 ff ff ff ff       mov    r8d,0xffffffff
  4005ad:       b9 22 00 00 00          mov    ecx,0x22
  4005b2:       ba 07 00 00 00          mov    edx,0x7
  4005b7:       be 00 10 00 00          mov    esi,0x1000
  4005bc:       bf 00 00 00 00          mov    edi,0x0
  4005c1:       e8 aa fe ff ff          call   400470 <mmap@plt>
  4005c6:       48 83 c4 08             add    rsp,0x8
  4005ca:       c3                      ret

00000000004005cb <main>:
  4005cb:       83 ff 01                cmp    edi,0x1
  4005ce:       7e 2f                   jle    4005ff <main+0x34>
  4005d0:       48 83 ec 08             sub    rsp,0x8
  4005d4:       b8 00 00 00 00          mov    eax,0x0
  4005d9:       e8 bf ff ff ff          call   40059d <call_mmap>
  4005de:       48 89 c2                mov    rdx,rax
  4005e1:       be 94 06 40 00          mov    esi,0x400694
  4005e6:       bf 01 00 00 00          mov    edi,0x1
  4005eb:       b8 00 00 00 00          mov    eax,0x0
  4005f0:       e8 ab fe ff ff          call   4004a0 <__printf_chk@plt>
  4005f5:       b8 00 00 00 00          mov    eax,0x0
  4005fa:       48 83 c4 08             add    rsp,0x8
  4005fe:       c3                      ret
  4005ff:       b8 00 00 00 00          mov    eax,0x0
  400604:       c3                      ret
  400605:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  40060c:       00 00 00
  40060f:       90                      nop

条件分岐はx86と同様にcmp命令とjle命令、関数の引数にはrdi/rsi/rdx/rcx/r8/r9レジスタを使い、戻り値はraxレジスタに入ることがわかる。

ARM

$ arm-linux-gnueabi-gcc -O1 hello.c -o hello-arm
$ objdump -d hello-arm
00008478 <call_mmap>:
    8478:       e52de004        push    {lr}            ; (str lr, [sp, #-4]!)
    847c:       e24dd00c        sub     sp, sp, #12
    8480:       e3e03000        mvn     r3, #0
    8484:       e58d3000        str     r3, [sp]
    8488:       e3a00000        mov     r0, #0
    848c:       e58d0004        str     r0, [sp, #4]
    8490:       e3a01a01        mov     r1, #4096       ; 0x1000
    8494:       e3a02007        mov     r2, #7
    8498:       e3a03022        mov     r3, #34 ; 0x22
    849c:       ebffff9f        bl      8320 <_init+0x38>
    84a0:       e28dd00c        add     sp, sp, #12
    84a4:       e8bd8000        ldmfd   sp!, {pc}

000084a8 <main>:
    84a8:       e92d4008        push    {r3, lr}
    84ac:       e3500001        cmp     r0, #1
    84b0:       da000004        ble     84c8 <main+0x20>
    84b4:       ebffffef        bl      8478 <call_mmap>
    84b8:       e1a02000        mov     r2, r0
    84bc:       e3a00001        mov     r0, #1
    84c0:       e59f1008        ldr     r1, [pc, #8]    ; 84d0 <main+0x28>
    84c4:       ebffff98        bl      832c <_init+0x44>
    84c8:       e3a00000        mov     r0, #0
    84cc:       e8bd8008        pop     {r3, pc}
    84d0:       00008548        .word   0x00008548

条件分岐にはcmp命令とble命令、関数の引数にはr0~r3レジスタとスタック、戻り値はr0レジスタに入ることがわかる。 また、関数呼び出しにはbl命令、関数リターンにはpcレジスタへの代入が使われている。

AArch64

AArch64はARMの64 bit版である。

$ aarch64-linux-gnu-gcc -O1 hello.c -o hello-aarch64
$ objdump -d hello-aarch64
00000000004005f0 <call_mmap>:
  4005f0:       a9bf7bfd        stp     x29, x30, [sp,#-16]!
  4005f4:       910003fd        mov     x29, sp
  4005f8:       d2800000        mov     x0, #0x0                        // #0
  4005fc:       d2820001        mov     x1, #0x1000                     // #4096
  400600:       528000e2        mov     w2, #0x7                        // #7
  400604:       52800443        mov     w3, #0x22                       // #34
  400608:       12800004        mov     w4, #0xffffffff                 // #-1
  40060c:       aa0003e5        mov     x5, x0
  400610:       97ffff9c        bl      400480 <mmap@plt>
  400614:       a8c17bfd        ldp     x29, x30, [sp],#16
  400618:       d65f03c0        ret

000000000040061c <main>:
  40061c:       a9bf7bfd        stp     x29, x30, [sp,#-16]!
  400620:       910003fd        mov     x29, sp
  400624:       7100041f        cmp     w0, #0x1
  400628:       540000ed        b.le    400644 <main+0x28>
  40062c:       97fffff1        bl      4005f0 <call_mmap>
  400630:       aa0003e2        mov     x2, x0
  400634:       52800020        mov     w0, #0x1                        // #1
  400638:       90000001        adrp    x1, 400000 <_init-0x400>
  40063c:       911ba021        add     x1, x1, #0x6e8
  400640:       97ffff84        bl      400450 <__printf_chk@plt>
  400644:       52800000        mov     w0, #0x0                        // #0
  400648:       a8c17bfd        ldp     x29, x30, [sp],#16
  40064c:       d65f03c0        ret

条件分岐にはcmp命令とb.le命令、関数の引数にはx0~x6レジスタ、戻り値はx0レジスタに入ることがわかる。 また、x86-64におけるrax(64 bitアクセス)とeax(32 bitアクセス)のようにx0とw0が対応している(参考)。 さらに、ARMにはなかったret命令が関数リターンで用いられている。

PowerPC

$ powerpc-linux-gnu-gcc -O1 hello.c -o hello-powerpc
$ objdump -d hello-powerpc
1000053c <call_mmap>:
1000053c:       94 21 ff f0     stwu    r1,-16(r1)
10000540:       7c 08 02 a6     mflr    r0
10000544:       90 01 00 14     stw     r0,20(r1)
10000548:       38 60 00 00     li      r3,0
1000054c:       38 80 10 00     li      r4,4096
10000550:       38 a0 00 07     li      r5,7
10000554:       38 c0 00 22     li      r6,34
10000558:       38 e0 ff ff     li      r7,-1
1000055c:       39 00 00 00     li      r8,0
10000560:       48 00 01 e1     bl      10000740 <mmap@plt>
10000564:       80 01 00 14     lwz     r0,20(r1)
10000568:       7c 08 03 a6     mtlr    r0
1000056c:       38 21 00 10     addi    r1,r1,16
10000570:       4e 80 00 20     blr

10000574 <main>:
10000574:       2f 83 00 01     cmpwi   cr7,r3,1
10000578:       40 9d 00 40     ble     cr7,100005b8 <main+0x44>
1000057c:       94 21 ff f0     stwu    r1,-16(r1)
10000580:       7c 08 02 a6     mflr    r0
10000584:       90 01 00 14     stw     r0,20(r1)
10000588:       4b ff ff b5     bl      1000053c <call_mmap>
1000058c:       7c 65 1b 78     mr      r5,r3
10000590:       38 60 00 01     li      r3,1
10000594:       3c 80 10 00     lis     r4,4096
10000598:       38 84 08 04     addi    r4,r4,2052
1000059c:       4c c6 31 82     crclr   4*cr1+eq
100005a0:       48 00 01 c1     bl      10000760 <__printf_chk@plt>
100005a4:       38 60 00 00     li      r3,0
100005a8:       80 01 00 14     lwz     r0,20(r1)
100005ac:       7c 08 03 a6     mtlr    r0
100005b0:       38 21 00 10     addi    r1,r1,16
100005b4:       4e 80 00 20     blr
100005b8:       38 60 00 00     li      r3,0
100005bc:       4e 80 00 20     blr

条件分岐にはcmpwi命令とble命令が用いられており、x86におけるeflag相当のレジスタとしてcr7レジスタが使われていることがわかる。 関数の引数にはr3~r8レジスタが使われており、戻り値はr3レジスタに入ることがわかる。 また、r0レジスタとr1レジスタx86におけるespレジスタebpレジスタに近い働きをしていることが推測できる。 なお、代入にはli命令、関数の呼び出しにはbl命令、関数リターンにはblr命令が用いられている。

PowerPC64

PowerPC64はその名の通りPowerPCの64 bit版である。

$ powerpc64le-linux-gnu-gcc -O1 hello.c -o hello-powerpc64le
$ objdump -d hello-powerpc64le
00000000100006b4 <call_mmap>:
    100006b4:   03 10 40 3c     lis     r2,4099
    100006b8:   10 80 42 38     addi    r2,r2,-32752
    100006bc:   a6 02 08 7c     mflr    r0
    100006c0:   10 00 01 f8     std     r0,16(r1)
    100006c4:   e1 ff 21 f8     stdu    r1,-32(r1)
    100006c8:   00 00 60 38     li      r3,0
    100006cc:   00 10 80 38     li      r4,4096
    100006d0:   07 00 a0 38     li      r5,7
    100006d4:   22 00 c0 38     li      r6,34
    100006d8:   ff ff e0 38     li      r7,-1
    100006dc:   00 00 00 39     li      r8,0
    100006e0:   41 fd ff 4b     bl      10000420 <00000017.plt_call.mmap@@GLIBC_2.17>
    100006e4:   18 00 41 e8     ld      r2,24(r1)
    100006e8:   20 00 21 38     addi    r1,r1,32
    100006ec:   10 00 01 e8     ld      r0,16(r1)
    100006f0:   a6 03 08 7c     mtlr    r0
    100006f4:   20 00 80 4e     blr
    100006f8:   00 00 00 00     .long 0x0
    100006fc:   00 00 00 01     .long 0x1000000
    10000700:   80 00 00 00     .long 0x80

0000000010000704 <main>:
    10000704:   03 10 40 3c     lis     r2,4099
    10000708:   10 80 42 38     addi    r2,r2,-32752
    1000070c:   01 00 83 2f     cmpwi   cr7,r3,1
    10000710:   40 00 9d 40     ble     cr7,10000750 <main+0x4c>
    10000714:   a6 02 08 7c     mflr    r0
    10000718:   10 00 01 f8     std     r0,16(r1)
    1000071c:   a1 ff 21 f8     stdu    r1,-96(r1)
    10000720:   9d ff ff 4b     bl      100006bc <call_mmap+0x8>
    10000724:   78 1b 65 7c     mr      r5,r3
    10000728:   01 00 60 38     li      r3,1
    1000072c:   fe ff 82 3c     addis   r4,r2,-2
    10000730:   68 89 84 38     addi    r4,r4,-30360
    10000734:   fd fc ff 4b     bl      10000430 <00000017.plt_call.__printf_chk@@GLIBC_2.17>
    10000738:   18 00 41 e8     ld      r2,24(r1)
    1000073c:   00 00 60 38     li      r3,0
    10000740:   60 00 21 38     addi    r1,r1,96
    10000744:   10 00 01 e8     ld      r0,16(r1)
    10000748:   a6 03 08 7c     mtlr    r0
    1000074c:   20 00 80 4e     blr
    10000750:   00 00 60 38     li      r3,0
    10000754:   20 00 80 4e     blr
    10000758:   00 00 00 00     .long 0x0
    1000075c:   00 00 00 01     .long 0x1000000
    10000760:   80 00 00 00     .long 0x80
    10000764:   00 00 00 60     nop
    10000768:   00 00 00 60     nop
    1000076c:   00 00 00 60     nop

条件分岐はPowerPCと同様にcmpwi命令とble命令、cr7レジスタが使われていることがわかる。 関数の引数、戻り値もPowerPC同様、r3~r8レジスタ、r3レジスタが用いられている。 また、PowerPCと比較して異なる点として、関数中でのr2レジスタの利用と関数末尾の定数3個(Function Descriptors)がある。

関連リンク