#!/usr/bin/perl -w
use strict;
# Tavis Ormandy <taviso@gentoo.org> 2003
# Improvments by Will Woods <wwoods@gentoo.org>
# Perl convertion by Georgi Georgiev <chutz@gg3.net>
#
# Identify instruction set used in binary.
#

# initialize everything to zero.
my ($i486,$i586,$ppro,$mmx,$sse,$sse2,$amd,$amd2,$cpuid) = (0,0,0,0,0,0,0,0,0);
my ($vendor, $subarch);
# unfortunately there are mnemonic collissions between vendor sets
# so check vendor_id string, and enable relevant sets.
print "Checking vendor_id string...";
my $param = shift;
unless (defined $param and $param eq "--vendor") {
	open PIPE, "grep -Em1 '^vendor_id.*: ' /proc/cpuinfo | cut -d' ' -f2 | " or die;
	$_ = <PIPE>;
	close PIPE;
	if (/GenuineIntel/)	{ $vendor="intel";	print "GenuineIntel\n" }
	elsif (/AuthenticAMD/)	{ $vendor="amd";	print "AuthenticAMD\n" }
	elsif (/CyrixInstead/)	{ $vendor="cyrix";	print "CyrixInstead\n" }
	elsif (/GenuineTMx86/)	{ $vendor="transmeta";	print "GenuineTMx86\n" }
	else 			{ $vendor="other";	print "other\n" }
} else {
	($param,$vendor) = split /=/, $param;
	printf "%s\n", $vendor;
	$param = shift;
}

# quick sanity tests.
defined $param	or die "usage: $0 [--vendor=intel|amd|cyrix|transmeta] /path/to/binary\n";
-e $param	or die "error: $param does not exist.\n";
-r $param	or die "error: cant read $param.\n";

printf "Disassembling %s, please wait...\n", $param;

# initialize screen output
if ($vendor eq "intel") {
	printf "i486: %4u i586: %4u ppro: %4u mmx: %4u sse: %4u sse2: %4u\r",
		$i486, $i586, $ppro, $mmx, $sse, $sse2;
} elsif ($vendor eq "amd") {
	printf "i486: %4u i586: %4u mmx: %4u sse: %4u 3dnow: %4u ext3dnow: %4u\r",
		$i486, $i586, $mmx, $sse, $amd, $amd2;
} elsif ($vendor eq "cyrix") {
	printf "i486: %4u i586: %4u mmx: %4u\r",
		$i486, $i586, $mmx;
} elsif ($vendor eq "transmeta") {
	printf "i486: %4u i586: %4u mmx: %4u\r",
		$i486, $i586, $mmx;
} else {
	printf "i486: %4u i586: %4u ppro: %4u mmx: %4u sse: %4u sse2: %4u\r",
		$i486, $i586, $ppro, $mmx, $sse, $sse2;
}

# do the disassembling.
open PIPE, "objdump -d $param | cut -f3 | cut -d' ' -f1 |" or die;
my $print;
while (defined (my $instruction = <PIPE>)) {
	chomp $instruction; 
	if (scalar (grep /^$instruction$/, "cmpxchg","xadd","bswap","invd","wbinvd","invlpg")) { $i486++; $print=1 }
	elsif (grep /^$instruction$/, "rdmsr","wrmsr","rdtsc","cmpxch8B","rsm") { $i586++; $print=1 }
	elsif (grep /^$instruction$/, "cmovcc","fcmovcc","fcomi","fcomip","fucomi","fucomip","rdpmc","ud2") { $ppro++; $print=1 }
	elsif (grep /^$instruction$/, "emms","movd","movq","packsswb","packssdw","packuswb","paddb","paddw","paddd","paddsb","paddsw","paddusb","paddusw","pand","pandn","pcmpeqb","pcmpeqw","pcmpeqd","pcmpgtb","pcmpgtw","pcmpgtd","pmaddwd","pmulhw","pmullw","por","psllw","pslld","psllq","psraw","psrad","psrlw","psrld","psrlq","psubb","psubw","psubd","psubsb","psubsw","psubusb","psubusw","punpckhbw","punpckhwd","punpckhdq","punpcklbw","punpcklwd","punpckldq","pxor")  { $mmx++; $print=1}
	elsif (grep /^$instruction$/, "addps","addss","andnps","andps","cmpps","cmpss","comiss","cvtpi2ps","cvtps2pi","cvtsi2ss","cvtss2si","cvttps2pi","cvttss2si","divps","divss","fxrstor","fxsave","ldmxcsr","maxps","maxss","minps","minss","movaps","movhlps","movhps","movlhps","movlps","movmskps","movss","movups","mulps","mulss","orps","pavgb","pavgw","psadbw","rcpps","rcpss","rsqrtps","rsqrtss","shufps","sqrtps","sqrtss","stmxcsr","subps","subss","ucomiss","unpckhps","unpcklps","xorps","pextrw","pinsrw","pmaxsw","pmaxub","pminsw","pminub","pmovmskb","pmulhuw","pshufw","maskmovq","movntps","movntq","prefetch","sfence") { $sse++; $print=1 }
	elsif (grep /^$instruction$/, "addpd","addsd","andnpd","andpd","clflush","cmppd","cmpsd","comisd","cvtdq2pd","cvtdq2ps","cvtpd2pi","cvtpd2pq","cvtpd2ps","cvtpi2pd","cvtps2dq","cvtps2pd","cvtsd2si","cvtsd2ss","cvtsi2sd","cvtss2sd","cvttpd2pi","cvttpd2dq","cvttps2dq","cvttsd2si","divpd","divsd","lfence","maskmovdqu","maxpd","maxsd","mfence","minpd","minsd","movapd","movd","movdq2q","movdqa","movdqu","movhpd","movlpd","movmskpd","movntdq","movnti","movntpd","movq","movq2dq","movsd","movupd","mulpd","mulsd","orpd","packsswb","packssdw","packuswb","paddb","paddw","paddd","paddq","paddq","paddsb","paddsw","paddusb","paddusw","pand","pandn","pause","pavgb","pavgw","pcmpeqb","pcmpeqw","pcmpeqd","pcmpgtb","pcmpgtw","pcmpgtd","pextrw","pinsrw","pmaddwd","pmaxsw","pmaxub","pminsw","pminub","pmovmskb","pmulhw","pmulhuw","pmullw","pmuludq","pmuludq","por","psadbw","pshufd","pshufhw","pshuflw","pslldq","psllw","pslld","psllq","psraw","psrad","psrldq","psrlw","psrld","psrlq","psubb","psubw","psubd","psubq","psubq","psubsb","psubsw","psubusb","psubusw","psubsb","punpckhbw","punpckhwd","punpckhdq","punpckhqdq","punpcklbw","punpcklwd","punpckldq","punpcklqdq","pxor","shufpd","sqrtpd","sqrtsd","subpd","subsd","ucomisd","unpckhpd","unpcklpd","xorpd") { $sse2++; $print=1 }
	elsif (grep /^$instruction$/, "pavgusb","pfadd","pfsub","pfsubr","pfacc","pfcmpge","pfcmpgt","pfcmpeq","pfmin","pfmax","pi2fw","pi2fd","pf2iw","pf2id","pfrcp","pfrsqrt","pfmul","pfrcpit1","pfrsqit1","pfrcpit2","pmulhrw","pswapw","femms","prefetch") { $amd++; $print=1 }
	elsif (grep /^$instruction$/, "pf2iw","pfnacc","pfpnacc","pi2fw","pswapd","maskmovq","movntq","pavgb","pavgw","pextrw","pinsrw","pmaxsw","pmaxub","pminsw","pminub","pmovmskb","pmulhuw","prefetchnta","prefetcht0","prefetcht1","prefetcht2","psadbw","pshufw","sfence") { $amd2++; $print=1 }
	elsif (grep /^$instruction$/, "cpuid") {$cpuid++, $i586++; $print=1 }
	if ($print) {
		if ($vendor eq "intel") {
			printf "i486: %4u i586: %4u ppro: %4u mmx: %4u sse: %4u sse2: %4u\r",
				$i486, $i586, $ppro, $mmx, $sse, $sse2;
		} elsif ($vendor eq "amd") {
			printf "i486: %4u i586: %4u mmx: %4u sse: %4u 3dnow: %4u ext3dnow: %4u\r",
				$i486, $i586, $mmx, $sse, $amd, $amd2;
		} elsif ($vendor eq "cyrix") {
			printf "i486: %4u i586: %4u mmx: %4u\r",
				$i486, $i586, $mmx;
		} elsif ($vendor eq "transmeta") {
			printf "i486: %4u i586: %4u mmx: %4u\r",
				$i486, $i586, $mmx;
		} else {
			printf "i486: %4u i586: %4u ppro: %4u mmx: %4u sse: %4u sse2: %4u\r",
				$i486, $i586, $ppro, $mmx, $sse, $sse2;
		}
		undef $print;
	}
}

# print a newline
print "\n";

# cpuid instruction could mean the application checks to see
# if an instruction is supported before executing it. This might 
# mean it will work on anything over a pentium.
if ($cpuid) {
	printf "\nThis binary was found to contain the cpuid instruction.\n";
	printf "It may be able to conditionally execute instructions if\n";
	printf "they are supported on the host (i586+).\n\n";
}

# print minimum required processor, if there are collissions
# use the vendor to decide what to print.
if ($sse2) {
	$subarch="Pentium IV (pentium4)"
} elsif ($sse) {
	if ($vendor eq "intel") {
		$subarch="Pentium III (pentium3)"
	} elsif ($vendor eq "amd") {
		$subarch="AMD Athlon 4 (athlon-4)"
	} else {
		$subarch="Pentium III (pentium3)"
	}
} elsif ($vendor eq "amd" and $amd2) {
	$subarch="AMD Athlon (athlon)"
} elsif ($vendor eq "amd" and $amd) {
	$subarch="AMD K6 III (k6-3)"
} elsif ($mmx) {
	if ($vendor eq "intel") {
		if ($ppro) {
			$subarch="Pentium II (pentium2)"
		} else {
			$subarch="Intel Pentium MMX [P55C] (pentium-mmx)"
		}
	} elsif ($vendor eq "amd") {
		$subarch="AMD K6 (k6)"
	} elsif ($vendor eq "cyrix") {
		$subarch="Cyrix 6x86MX / MII (pentium-mmx)"
	} else {
		$subarch="Intel Pentium MMX [P55C] (pentium-mmx)"
	}
} elsif ($ppro) {
	$subarch="Pentium Pro (i686 or pentiumpro)"
} elsif ($i586) {
	$subarch="Pentium or compatible (i586) (i586 or pentium)"
} elsif ($i486) {
	$subarch="80486 or comaptible (i486)"
} else {
	$subarch="80386 or compatible (i386)"
}

# print message and exit.
printf "%s will run on %s or higher processor.\n", $param, $subarch;


