Help us understand the problem. What is going on with this article?

複数の処理系で逆アセンブルして比較

More than 1 year has passed since last update.

Common Lisp には関数を逆アセンブルする disassemble 関数があります。

大熱血!アセンブラ入門』のサンプルコード(C言語)から移植した簡単な関数を逆アセンブルして比較します。

対象とする処理系は以下の3つです。

  • ECL (Embeddable Common-Lisp)
  • CLISP
  • SBCL (Steel Bank Common Lisp)

実行環境は Windows 10 上の WSL です。SBCL と CLISP は 1803 (17134.81) から動くようになりました。👉参考ツイート

disassemble 関数は『実用Common Lisp』読書会で教えて頂きました。Common Lisp が低レイヤも透過的だとは知らなかったので驚きました。

実行例

REPL で関数を定義して逆アセンブルします。

移植元となるC言語の関数です。

int return_zero()
{
  return 0;
}

ECL

処理系独自の中間コードにコンパイルされます。

> (defun return_zero () 0)

RETURN_ZERO
> (disassemble 'return_zero)

#(RETURN_ZERO #<bytecompiled-function RETURN_ZERO> SI:FSET)
Name:           RETURN_ZERO
   0    NOMORE
   1    QUOTE   0
   3    SET     VALUES(0),REG0
   4    EXIT
NIL

ネイティブコードにコンパイルすると逆アセンブルできなくなります。

> (compile 'return_zero)

;;; Loading #P"/usr/lib/ecl-15.3.7/cmp.fas"
;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=0
;;;
;;; End of Pass 1.
RETURN_ZERO
NIL
NIL
> (disassemble 'return_zero)
;;; Warning: Cannot disassemble the binary function #<compiled-function RETURN_ZERO> because I do not have its source code.

NIL

CLISP

こちらも中間コードですが ECL よりもシンプルです。

[1]> (defun return_zero () 0)
RETURN_ZERO
[2]> (disassemble 'return_zero)

Disassembly of function RETURN_ZERO
(CONST 0) = 0
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (CONST 0)                           ; 0
1     (SKIP&RET 1)
NIL

ECL とは異なり、コンパイルしても中間コードで逆アセンブルされます。

SBCL

明示的にコンパイルしなくても、定義しただけでネイティブコードにコンパイルされます。

* (defun return_zero () 0)

RETURN_ZERO
* (disassemble 'return_zero)

; disassembly for RETURN_ZERO
; Size: 19 bytes. Origin: #x10039D0784
; 84:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 89:       48894DF8         MOV [RBP-8], RCX
; 8D:       31D2             XOR EDX, EDX
; 8F:       488BE5           MOV RSP, RBP
; 92:       F8               CLC
; 93:       5D               POP RBP
; 94:       C3               RET
; 95:       CC10             BREAK 16                         ; Invalid argument count trap
NIL

ABI はC言語とは異なっているようで良く分かりませんが、EDX が戻り値のようです。

1を返す

結果のみを掲載します。

int return_one()
{
  return 1;
}
(defun return_one () 1)
ECL
#(RETURN_ONE #<bytecompiled-function RETURN_ONE> SI:FSET)
Name:           RETURN_ONE
   0    NOMORE
   1    QUOTE   1
   3    SET     VALUES(0),REG0
   4    EXIT
CLISP
Disassembly of function RETURN_ONE
(CONST 0) = 1
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (CONST 0)                           ; 1
1     (SKIP&RET 1)
SBCL
; disassembly for RETURN_ONE
; Size: 22 bytes. Origin: #x1003A113D4
; D4:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; D9:       48894DF8         MOV [RBP-8], RCX
; DD:       BA02000000       MOV EDX, 2
; E2:       488BE5           MOV RSP, RBP
; E5:       F8               CLC
; E6:       5D               POP RBP
; E7:       C3               RET
; E8:       CC10             BREAK 16                         ; Invalid argument count trap

SBCL で戻り値の EDX2 になっていることから、数値以外の情報が含まれているようです。

定数を返す

short return_short()
{
  return 0x7788;
}

long return_long()
{
  return 0x778899aa;
}

long return_long_upper()
{
  return 0xffeeddcc;
}
(defun return_short () #x7788)
(defun return_long () #x778899aa)
(defun return_long_upper () #xffeeddcc)
ECL
#(RETURN_SHORT #<bytecompiled-function RETURN_SHORT> SI:FSET)
Name:           RETURN_SHORT
   0    NOMORE
   1    QUOTE   30600
   3    SET     VALUES(0),REG0
   4    EXIT
#(RETURN_LONG 2005440938 #<bytecompiled-function RETURN_LONG> SI:FSET)
Name:           RETURN_LONG
   0    NOMORE
   1    QUOTE   2005440938
   3    SET     VALUES(0),REG0
   4    EXIT
#(RETURN_LONG_UPPER 4293844428 #<bytecompiled-function RETURN_LONG_UPPER> SI:FSET)
Name:           RETURN_LONG_UPPER
   0    NOMORE
   1    QUOTE   4293844428
   3    SET     VALUES(0),REG0
   4    EXIT
CLISP
Disassembly of function RETURN_SHORT
(CONST 0) = 30600
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (CONST 0)                           ; 30600
1     (SKIP&RET 1)

Disassembly of function RETURN_LONG
(CONST 0) = 2005440938
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (CONST 0)                           ; 2005440938
1     (SKIP&RET 1)

Disassembly of function RETURN_LONG_UPPER
(CONST 0) = 4293844428
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (CONST 0)                           ; 4293844428
1     (SKIP&RET 1)
SBCL
; disassembly for RETURN_SHORT
; Size: 22 bytes. Origin: #x1003C1F254
; 54:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 59:       48894DF8         MOV [RBP-8], RCX
; 5D:       BA10EF0000       MOV EDX, 61200
; 62:       488BE5           MOV RSP, RBP
; 65:       F8               CLC
; 66:       5D               POP RBP
; 67:       C3               RET
; 68:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for RETURN_LONG
; Size: 22 bytes. Origin: #x1003C34F44
; 44:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 49:       48894DF8         MOV [RBP-8], RCX
; 4D:       BA543311EF       MOV EDX, -284085420
; 52:       488BE5           MOV RSP, RBP
; 55:       F8               CLC
; 56:       5D               POP RBP
; 57:       C3               RET
; 58:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for RETURN_LONG_UPPER
; Size: 27 bytes. Origin: #x1003C4AD94
; 94:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 99:       48894DF8         MOV [RBP-8], RCX
; 9D:       48BA98BBDDFF01000000 MOV RDX, 8587688856
; A7:       488BE5           MOV RSP, RBP
; AA:       F8               CLC
; AB:       5D               POP RBP
; AC:       C3               RET
; AD:       CC10             BREAK 16                         ; Invalid argument count trap

CLISP では関数ごとに定数テーブルが存在するようです。
SBCL ではやはり数値が倍になっているようです。

引数

int return_arg1(int a)
{
  return a;
}

int return_arg2(int a, int b)
{
  return b;
}
(defun return_arg1 (a) a)
(defun return_arg2 (a b) b)
ECL
#(RETURN_ARG1 A #<bytecompiled-function RETURN_ARG1> SI:FSET)
Name:           RETURN_ARG1
   0    POP     REQ
   1    BIND    A
   3    NOMORE
   4    VAR     0
   6    SET     VALUES(0),REG0
   7    EXIT
#(RETURN_ARG2 A B #<bytecompiled-function RETURN_ARG2> SI:FSET)
Name:           RETURN_ARG2
   0    POP     REQ
   1    BIND    A
   3    POP     REQ
   4    BIND    B
   6    NOMORE
   7    VAR     0
   9    SET     VALUES(0),REG0
  10    EXIT
CLISP
Disassembly of function RETURN_ARG1
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (LOAD 1)
1     (SKIP&RET 2)
WARNING: in RETURN_ARG2 : variable A is not used.
         Misspelled or missing IGNORE declaration?

Disassembly of function RETURN_ARG2
2 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (LOAD 1)
1     (SKIP&RET 3)
SBCL
; disassembly for RETURN_ARG1
; Size: 17 bytes. Origin: #x1003A4D165
; 65:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 6A:       48894DF8         MOV [RBP-8], RCX
; 6E:       488BE5           MOV RSP, RBP
; 71:       F8               CLC
; 72:       5D               POP RBP
; 73:       C3               RET
; 74:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for RETURN_ARG2
; Size: 17 bytes. Origin: #x1003A6F288
; 88:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 8D:       48894DF8         MOV [RBP-8], RCX
; 91:       488BE5           MOV RSP, RBP
; 94:       F8               CLC
; 95:       5D               POP RBP
; 96:       C3               RET
; 97:       CC10             BREAK 16                         ; Invalid argument count trap

ECL はスタックマシンのようです。
SBCL は2つの関数が機械語レベルで同一です。

足し算

int add(int a, int b)
{
  return a + b;
}

int add3(int a, int b, int c)
{
  return a + b + c;
}

int add_two(int a)
{
  return a + 2;
}

int inc(int a)
{
  return ++a;
}
(defun add (a b) (+ a b))
(defun add3 (a b c) (+ a b c))
(defun add_two (a) (+ a 2))
(defun inc (a) (+ a 1))
ECL
#(ADD A B + #<bytecompiled-function ADD> SI:FSET)
Name:           ADD
   0    POP     REQ
   1    BIND    A
   3    POP     REQ
   4    BIND    B
   6    NOMORE
   7    PUSHV   1
   9    PUSHV   0
  11    CALLG   2,+
  14    EXIT
#(ADD3 A B C + #<bytecompiled-function ADD3> SI:FSET)
Name:           ADD3
   0    POP     REQ
   1    BIND    A
   3    POP     REQ
   4    BIND    B
   6    POP     REQ
   7    BIND    C
   9    NOMORE
  10    PUSHV   2
  12    PUSHV   1
  14    PUSHV   0
  16    CALLG   3,+
  19    EXIT
#(ADD_TWO A + #<bytecompiled-function ADD_TWO> SI:FSET)
Name:           ADD_TWO
   0    POP     REQ
   1    BIND    A
   3    NOMORE
   4    PUSHV   0
   6    PUSH    2
   8    CALLG   2,+
  11    EXIT
#(INC A + #<bytecompiled-function INC> SI:FSET)
Name:           INC
   0    POP     REQ
   1    BIND    A
   3    NOMORE
   4    PUSHV   0
   6    PUSH    1
   8    CALLG   2,+
  11    EXIT
CLISP
Disassembly of function ADD
2 required arguments
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0     (LOAD&PUSH 2)
1     (LOAD&PUSH 2)
2     (CALLSR 2 55)                       ; +
5     (SKIP&RET 3)

Disassembly of function ADD3
3 required arguments
0 optional arguments
No rest parameter
No keyword parameters
5 byte-code instructions:
0     (LOAD&PUSH 3)
1     (LOAD&PUSH 3)
2     (LOAD&PUSH 3)
3     (CALLSR 3 55)                       ; +
6     (SKIP&RET 4)

Disassembly of function ADD_TWO
(CONST 0) = 2
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0     (CONST&PUSH 0)                      ; 2
1     (LOAD&PUSH 2)
2     (CALLSR 2 55)                       ; +
5     (SKIP&RET 2)

Disassembly of function INC
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
3 byte-code instructions:
0     (LOAD&PUSH 1)
1     (CALLS2 177)                        ; 1+
3     (SKIP&RET 2)
SBCL
; disassembly for ADD
; Size: 40 bytes. Origin: #x1003B54F73
; 73:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 78:       48894DF8         MOV [RBP-8], RCX
; 7C:       488BD6           MOV RDX, RSI
; 7F:       488BFB           MOV RDI, RBX
; 82:       41BBC0010020     MOV R11D, 536871360              ; GENERIC-+
; 88:       41FFD3           CALL R11
; 8B:       488B5DE8         MOV RBX, [RBP-24]
; 8F:       488B75F0         MOV RSI, [RBP-16]
; 93:       488BE5           MOV RSP, RBP
; 96:       F8               CLC
; 97:       5D               POP RBP
; 98:       C3               RET
; 99:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for ADD3
; Size: 47 bytes. Origin: #x1003B77CF7
; CF7:       498B4C2460       MOV RCX, [R12+96]               ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; CFC:       48894DF8         MOV [RBP-8], RCX
; D00:       488B55F0         MOV RDX, [RBP-16]
; D04:       488B7DE8         MOV RDI, [RBP-24]
; D08:       41BBC0010020     MOV R11D, 536871360             ; GENERIC-+
; D0E:       41FFD3           CALL R11
; D11:       488B7DE0         MOV RDI, [RBP-32]
; D15:       41BBC0010020     MOV R11D, 536871360             ; GENERIC-+
; D1B:       41FFD3           CALL R11
; D1E:       488BE5           MOV RSP, RBP
; D21:       F8               CLC
; D22:       5D               POP RBP
; D23:       C3               RET
; D24:       CC10             BREAK 16                        ; Invalid argument count trap
; disassembly for ADD_TWO
; Size: 38 bytes. Origin: #x1003B97E0C
; 0C:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 11:       48894DF8         MOV [RBP-8], RCX
; 15:       BF04000000       MOV EDI, 4
; 1A:       488BD3           MOV RDX, RBX
; 1D:       41BBC0010020     MOV R11D, 536871360              ; GENERIC-+
; 23:       41FFD3           CALL R11
; 26:       488B5DF0         MOV RBX, [RBP-16]
; 2A:       488BE5           MOV RSP, RBP
; 2D:       F8               CLC
; 2E:       5D               POP RBP
; 2F:       C3               RET
; 30:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for INC
; Size: 38 bytes. Origin: #x1003BB3ABC
; BC:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; C1:       48894DF8         MOV [RBP-8], RCX
; C5:       BF02000000       MOV EDI, 2
; CA:       488BD3           MOV RDX, RBX
; CD:       41BBC0010020     MOV R11D, 536871360              ; GENERIC-+
; D3:       41FFD3           CALL R11
; D6:       488B5DF0         MOV RBX, [RBP-16]
; DA:       488BE5           MOV RSP, RBP
; DD:       F8               CLC
; DE:       5D               POP RBP
; DF:       C3               RET
; E0:       CC10             BREAK 16                         ; Invalid argument count trap

CLISP では呼び出す関数が 1+ という別の関数になっています。

関数呼び出し

既に足し算で関数呼び出しが現れていますが、一応確認します。

int call_simple(int a)
{
  return return_arg1(a);
}

int call_complex1()
{
  return return_arg1(0xfe) + 1;
}
(defun call_simple (a) (return_arg1 a))
(defun call_complex1 () (+ (return_arg1 #xfe) 1))
ECL
#(CALL_SIMPLE A RETURN_ARG1 #<bytecompiled-function CALL_SIMPLE> SI:FSET)
Name:           CALL_SIMPLE
   0    POP     REQ
   1    BIND    A
   3    NOMORE
   4    PUSHV   0
   6    CALLG   1,RETURN_ARG1
   9    EXIT
#(CALL_COMPLEX1 RETURN_ARG1 + #<bytecompiled-function CALL_COMPLEX1> SI:FSET)
Name:           CALL_COMPLEX1
   0    NOMORE
   1    PUSH    254
   3    CALLG   1,RETURN_ARG1
   6    PUSH    VALUES(0)
   7    PUSH    1
   9    CALLG   2,+
  12    EXIT
CLISP
Disassembly of function CALL_SIMPLE
(CONST 0) = RETURN_ARG1
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
3 byte-code instructions:
0     (LOAD&PUSH 1)
1     (CALL1 0)                           ; RETURN_ARG1
3     (SKIP&RET 2)

Disassembly of function CALL_COMPLEX1
(CONST 0) = 254
(CONST 1) = RETURN_ARG1
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
4 byte-code instructions:
0     (CONST&PUSH 0)                      ; 254
1     (CALL1&PUSH 1)                      ; RETURN_ARG1
3     (CALLS2 177)                        ; 1+
5     (SKIP&RET 1)
SBCL
; disassembly for CALL_SIMPLE
; Size: 32 bytes. Origin: #x1003C88628
; 28:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 2D:       48894DF8         MOV [RBP-8], RCX
; 31:       488BD6           MOV RDX, RSI
; 34:       488B0595FFFFFF   MOV RAX, [RIP-107]               ; #<FDEFINITION for RETURN_ARG1>
; 3B:       B902000000       MOV ECX, 2
; 40:       FF7508           PUSH QWORD PTR [RBP+8]
; 43:       FF6009           JMP QWORD PTR [RAX+9]
; 46:       CC10             BREAK 16                         ; Invalid argument count trap
; disassembly for CALL_COMPLEX1
; Size: 70 bytes. Origin: #x1003CA2D04
; 04:       498B4C2460       MOV RCX, [R12+96]                ; thread.binding-stack-pointer
                                                              ; no-arg-parsing entry point
; 09:       48894DF8         MOV [RBP-8], RCX
; 0D:       488D5C24F0       LEA RBX, [RSP-16]
; 12:       4883EC18         SUB RSP, 24
; 16:       BAFC010000       MOV EDX, 508
; 1B:       488B058EFFFFFF   MOV RAX, [RIP-114]               ; #<FDEFINITION for RETURN_ARG1>
; 22:       B902000000       MOV ECX, 2
; 27:       48892B           MOV [RBX], RBP
; 2A:       488BEB           MOV RBP, RBX
; 2D:       FF5009           CALL QWORD PTR [RAX+9]
; 30:       480F42E3         CMOVB RSP, RBX
; 34:       BF02000000       MOV EDI, 2
; 39:       41BBC0010020     MOV R11D, 536871360              ; GENERIC-+
; 3F:       41FFD3           CALL R11
; 42:       488BE5           MOV RSP, RBP
; 45:       F8               CLC
; 46:       5D               POP RBP
; 47:       C3               RET
; 48:       CC10             BREAK 16                         ; Invalid argument count trap

まとめ

きりがないのでこの辺にします。

  • ECL: Java のバイトコードに似たスタックマシンのようです。
  • CLISP: VLIW に似た複合命令のようです。
  • SBCL: ネイティブコードだと抽象化されていないため、読むのは結構大変そうです。

簡単に確認できるので、興味があるコードは自分で試してみると良いでしょう。

7shi
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away