« 4/22(水)Shibuya.pm#11開催します | メイン

[Debug Hacks] #66.手元のx86マシンが64bitモード対応かどうかを調べる

本日オライリージャパン様より「Debug Hacks――デバッグを極めるテクニック&ツール」の献本をいただきました。著者の皆様、出版社の皆様ありがとうございます。

とりあえず、ざっくりと気になる章だけをかいつまんで読んでみたのですが、最後の章「#66.手元のx86マシンが64bitモード対応かどうかを調べる」では、/proc/cpuinfo で lm の文字列を探す方法と、以下のような CPUID 命令を発行して今自分が使っているマシンのCPUが64bitに対応しているかどうかを調べるハックが紹介されていました。

#include <stdio.h>

void cpuid(int op, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) {
  __asm__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (op));
}


int main() {
  unsigned int eax, ebx, ecx, edx;
  cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
  if (eax < 0x80000001) return 0;
  cpuid(0x80000001, &eax, &ebx, &ecx, &edx);
  if (!((1<<29) & edx)) return 0;
  printf("64bit Long mode is supported.\n");
  return 1;
}

ただし、このハックの問題点は、CPUが64bitをサポートしているかどうかだけをチェックしていて、実際に64bitロングモードか32bitのx86互換モードで動いているかどうかを検出できないことにあります。

そこで、現在のCPUが64bitロングモードで動作しているかどうかをチェックする簡単なPerlスクリプトを書いてみました。

#!/usr/bin/perl

use DynaLoader;

sub x86_sub {
	my ($func, $x86) = @_;
	if ($^O eq "linux") {
		require 'syscall.ph';
		syscall(&SYS_mprotect,(unpack"L",pack"P",$x86)&~4095,4096*2,7);
	}
	DynaLoader::dl_install_xsub(caller(0)."::$func",unpack"L",pack"P",$x86);
}

x86_sub check64bit => do {
	our $LM64bit = "?";

	"\xb8\x31\x00\x00\x00".    # mov eax, 0x31

	"\x48".                    # dec eax // 64bit REX PREFIX
	"\xa2".pack("P",$LM64bit). # mov [$LM64bit], al
	"\xc3";                    # ret

};

&check64bit();

warn $LM64bit; # "0" => 32bit, "1" => 64bit

これは、DynaLoader::dl_install_xsubでx86の機械語を直接実行させるサブルーチンを定義して、64bitロングモード特有のREX PREFIXを解釈するかどうかで判別している方法になります。32bitのx86互換モードでは dec eax が実行され $LM64bit='0' となりますが、64bitロングモードではこのような1byte decは解釈されず、直後のmov命令のREX PREFIXを指定することになるので、$LM64bit='1' となります。

したがって、x86_64対応の「Intel(R) Xeon(R) CPU E5430 @ 2.66GHz」が搭載されているマシンでもCentOS 5.2(i386)のような64bitに対応していないOSで動かしてしまっている場合は、32bitと判定されます。

本Hackはデバッグには関係ありませんが、普段スクリプト言語を書いておられる方にも興味を持っていただければ幸いです。

コメント

うーん、Leopard で -Duse64bitall な perlだと SEGV で落ちちゃいました。

% ~/perl5/maint64/bin/perl -V | grep 64bit
config_args='-DDEBUGGING -Doptimize=-g -pipe -Os -Dusethreads -Dusemultiplicity -Duse64bitall -Dprefix=/Users/dankogai/perl5/maint64 -des'
use64bitint=define, use64bitall=define, uselongdouble=undef

32bit版ではきちんと0と出ます。

Dan the Man with too Many Perl Versions to Support

すみません。Leopard では動作確認していませんでした。
32bit環境の場合は length(pack'P',$_) == 4、64bit環境の場合は length(pack'P',$_) == 8 が前提となっているコードなので、
実行時のモードとポインタのサイズが異なると NULL pointerアクセスあるいは ret が実行されずに SEGV してしまうかもしれません。

コメントを投稿