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)
#(RETURN_ONE #<bytecompiled-function RETURN_ONE> SI:FSET)
Name: RETURN_ONE
0 NOMORE
1 QUOTE 1
3 SET VALUES(0),REG0
4 EXIT
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)
; 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 で戻り値の EDX
が 2
になっていることから、数値以外の情報が含まれているようです。
定数を返す
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)
#(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
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)
; 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)
#(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
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)
; 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))
#(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
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)
; 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))
#(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
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)
; 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: ネイティブコードだと抽象化されていないため、読むのは結構大変そうです。
簡単に確認できるので、興味があるコードは自分で試してみると良いでしょう。