lp64データモデルにおける関数ポインタ使用の留意点

こんにちは、wattak777です。

自分は主に C/C++ を組むことが多いのですが、ちょっと前にハマったことを。
下記がそのサンプルプログラム。

```c:test.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したものが以下。

```asm:objdumpしたアセンブリ言語ソース
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 とビット幅を明記して記載するようにしています（例外はありますが）。