こんにちは、wattak777です。
自分は主に C/C++ を組むことが多いのですが、ちょっと前にハマったことを。
下記がそのサンプルプログラム。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
long ip_test( void ) ;
int i_test( void ) ;
short s_test( void ) ;
char c_test( void ) ;
int main( int argc, char* argv[] )
{
long (*p_func)() ;
long ret ;
p_func = (long(*)())ip_test ;
ret = (*p_func)() ;
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
p_func = (long(*)())i_test ;
ret = (*p_func)() ;
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
p_func = (long(*)())s_test ;
ret = (*p_func)() ;
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
p_func = (long(*)())c_test ;
ret = (*p_func)() ;
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
}
long ip_test( void )
{
return -1 ;
}
int i_test( void )
{
return -1 ;
}
short s_test( void )
{
return -1 ;
}
char c_test( void )
{
return -1 ;
}
これを以下のlp64データモデル環境でビルドして実行させてみます。
環境
CPU:Intel Corei7(x86_64)
OS:Ubuntu 18.04 LTS
gcc:バージョン 7.5.0
ip_test[0xffffffffffffffff] [-1]
ip_test[0x00000000ffffffff] [4294967295]
ip_test[0x00000000ffffffff] [4294967295]
ip_test[0x00000000ffffffff] [4294967295]
要は、int型のi_test関数、short型のs_test関数、char型のc_test関数の戻り値をいくらlongを戻り値に持つ関数ポインタにキャストしても上位32bitは設定されない、ということですね。
実行ファイルをobjdump -Sしたものが以下。
a.out: ファイル形式 elf64-x86-64
(中略)
000000000000064a <main>:
int i_test( void ) ;
short s_test( void ) ;
char c_test( void ) ;
int main( int argc, char* argv[] )
{
64a: 55 push %rbp
64b: 48 89 e5 mov %rsp,%rbp
64e: 48 83 ec 20 sub $0x20,%rsp
652: 89 7d ec mov %edi,-0x14(%rbp)
655: 48 89 75 e0 mov %rsi,-0x20(%rbp)
long (*p_func)() ;
long ret ;
p_func = (long(*)())ip_test ;
659: 48 8d 05 d8 00 00 00 lea 0xd8(%rip),%rax # 738 <ip_test>
660: 48 89 45 f0 mov %rax,-0x10(%rbp)
ret = (*p_func)() ;
664: 48 8b 55 f0 mov -0x10(%rbp),%rdx
668: b8 00 00 00 00 mov $0x0,%eax
66d: ff d2 callq *%rdx
66f: 48 89 45 f8 mov %rax,-0x8(%rbp)
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
673: 48 8b 55 f8 mov -0x8(%rbp),%rdx
677: 48 8b 45 f8 mov -0x8(%rbp),%rax
67b: 48 89 c6 mov %rax,%rsi
67e: 48 8d 3d 6f 01 00 00 lea 0x16f(%rip),%rdi # 7f4 <_IO_stdin_used+0x4>
685: b8 00 00 00 00 mov $0x0,%eax
68a: e8 91 fe ff ff callq 520 <printf@plt>
p_func = (long(*)())i_test ;
68f: 48 8d 05 af 00 00 00 lea 0xaf(%rip),%rax # 745 <i_test>
696: 48 89 45 f0 mov %rax,-0x10(%rbp)
ret = (*p_func)() ;
69a: 48 8b 55 f0 mov -0x10(%rbp),%rdx
69e: b8 00 00 00 00 mov $0x0,%eax
6a3: ff d2 callq *%rdx
6a5: 48 89 45 f8 mov %rax,-0x8(%rbp)
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
6a9: 48 8b 55 f8 mov -0x8(%rbp),%rdx
6ad: 48 8b 45 f8 mov -0x8(%rbp),%rax
6b1: 48 89 c6 mov %rax,%rsi
6b4: 48 8d 3d 39 01 00 00 lea 0x139(%rip),%rdi # 7f4 <_IO_stdin_used+0x4>
6bb: b8 00 00 00 00 mov $0x0,%eax
6c0: e8 5b fe ff ff callq 520 <printf@plt>
p_func = (long(*)())s_test ;
6c5: 48 8d 05 84 00 00 00 lea 0x84(%rip),%rax # 750 <s_test>
6cc: 48 89 45 f0 mov %rax,-0x10(%rbp)
ret = (*p_func)() ;
6d0: 48 8b 55 f0 mov -0x10(%rbp),%rdx
6d4: b8 00 00 00 00 mov $0x0,%eax
6d9: ff d2 callq *%rdx
6db: 48 89 45 f8 mov %rax,-0x8(%rbp)
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
6df: 48 8b 55 f8 mov -0x8(%rbp),%rdx
6e3: 48 8b 45 f8 mov -0x8(%rbp),%rax
6e7: 48 89 c6 mov %rax,%rsi
6ea: 48 8d 3d 03 01 00 00 lea 0x103(%rip),%rdi # 7f4 <_IO_stdin_used+0x4>
6f1: b8 00 00 00 00 mov $0x0,%eax
6f6: e8 25 fe ff ff callq 520 <printf@plt>
p_func = (long(*)())c_test ;
6fb: 48 8d 05 59 00 00 00 lea 0x59(%rip),%rax # 75b <c_test>
702: 48 89 45 f0 mov %rax,-0x10(%rbp)
ret = (*p_func)() ;
706: 48 8b 55 f0 mov -0x10(%rbp),%rdx
70a: b8 00 00 00 00 mov $0x0,%eax
70f: ff d2 callq *%rdx
711: 48 89 45 f8 mov %rax,-0x8(%rbp)
printf( "ip_test[0x%016lx] [%ld]\n", ret, ret ) ;
715: 48 8b 55 f8 mov -0x8(%rbp),%rdx
719: 48 8b 45 f8 mov -0x8(%rbp),%rax
71d: 48 89 c6 mov %rax,%rsi
720: 48 8d 3d cd 00 00 00 lea 0xcd(%rip),%rdi # 7f4 <_IO_stdin_used+0x4>
727: b8 00 00 00 00 mov $0x0,%eax
72c: e8 ef fd ff ff callq 520 <printf@plt>
731: b8 00 00 00 00 mov $0x0,%eax
}
736: c9 leaveq
737: c3 retq
0000000000000738 <ip_test>:
long ip_test( void )
{
738: 55 push %rbp
739: 48 89 e5 mov %rsp,%rbp
return -1 ;
73c: 48 c7 c0 ff ff ff ff mov $0xffffffffffffffff,%rax
}
743: 5d pop %rbp
744: c3 retq
0000000000000745 <i_test>:
int i_test( void )
{
745: 55 push %rbp
746: 48 89 e5 mov %rsp,%rbp
return -1 ;
749: b8 ff ff ff ff mov $0xffffffff,%eax
}
74e: 5d pop %rbp
74f: c3 retq
0000000000000750 <s_test>:
short s_test( void )
{
750: 55 push %rbp
751: 48 89 e5 mov %rsp,%rbp
return -1 ;
754: b8 ff ff ff ff mov $0xffffffff,%eax
}
759: 5d pop %rbp
75a: c3 retq
000000000000075b <c_test>:
char c_test( void )
{
75b: 55 push %rbp
75c: 48 89 e5 mov %rsp,%rbp
return -1 ;
75f: b8 ff ff ff ff mov $0xffffffff,%eax
}
764: 5d pop %rbp
765: c3 retq
766: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
76d: 00 00 00
(以下略)
上記の変換されたソースを見たところ、32bit型の戻り値はeaxレジスタへ、64bit型の戻り値はraxレジスタへセットされるためそのあと32bit型のものは64bitへキャストしても上位32bitがゼロの状態になってしまうようです。
サンプルはx86_64系CPU上での動作結果ですがarm64系も同じような現象になっていましたので同じようなアセンブリ言語になるかと。
(gccのバージョンにより、上記のような動作をしない可能性もあります)
仮に、サンプルがそれまでilp32データモデルのCPUで動作していた場合、lp64データモデルのCPUへポーティングしたりすると、今まで「たまたま」動作していたものが動かなくなる、ということもあり得る、という例でした。
※ちなみに、筆者は C/C++ で long/int/short/char は書かず、それぞれ int64_t/int32_t/int16_t/int8_t とビット幅を明記して記載するようにしています(例外はありますが)。