Mi sono imbattuto nello stesso problema mentre cercavo di comprendere i processi di ottimizzazione di GCC e di scoprire quali istruzioni sono state utilizzate o meno durante questo processo. Dato che non sono amichevole con l'enorme numero di codici operativi, stavo cercando un modo per visualizzare istruzioni specifiche (diciamo SSE3) all'interno del codice disassemblato, o almeno per stampare alcune statistiche minime come se e quante di queste istruzioni ci sono nel binario.
Non ho trovato alcuna soluzione esistente, ma la risposta di Jonathan Ben-Avraham si è rivelata molto utile, poiché indica un'ottima (e anche parzialmente strutturata) fonte di codici operativi. Sulla base di questi dati, ho scritto uno script Bash che può visualizzare specifici set di istruzioni o stampare statistiche su di essi usando grep
quando alimentato con l'output da objdump
.
L'elenco dei codici operativi è stato convertito in uno script Bash autonomo che viene poi incluso (per una migliore leggibilità) nel file principale che ho chiamato semplicemente opcode
. Poiché i codici operativi in gas.vim
(Il vim
di Shirk le definizioni di sintassi, dalla risposta di Jonathan) sono state sistematicamente raggruppate (apparentemente) in base a diverse architetture di CPU, ho cercato di preservare questa divisione e creare un set di istruzioni architettura-> Mappatura; Ora non sono sicuro che sia stata una buona idea. La mappatura non è accurata e ho anche dovuto apportare alcune modifiche al gas.vim
originale raggruppamento. Poiché i set di istruzioni relativi all'architettura non erano la mia intenzione originale, ho provato a costruire solo set di istruzioni delle principali architetture descritte su Internet, ma senza consultare la documentazione dei produttori. Le architetture AMD non mi sembrano affatto affidabili (eccetto i set di istruzioni come 3DNow! e SSE5). Tuttavia, ho deciso di lasciare qui il codice per i set di istruzioni di varie architetture affinché qualcun altro possa esaminarlo e correggerlo/migliorarlo e dare ad altri alcuni risultati provvisori.
Inizio del file principale denominato opcode
:
#!/bin/bash
#
# Searches disassembled code for specific instructions.
#
# Opcodes obtained from: https://github.com/Shirk/vim-gas/blob/master/syntax/gas.vim
#
# List of opcodes has been obtained using the following commands and making a few modifications:
# echo '#!/bin/bash' > Opcode_list
# wget -q -O- https://raw.githubusercontent.com/Shirk/vim-gas/master/syntax/gas.vim \
# | grep -B1 -E 'syn keyword gasOpcode_|syn match gasOpcode' | \
# sed -e '/^--$/d' -e 's/"-- Section:/\n#/g' \
# -e 's/syn keyword gasOpcode_\([^\t]*\)*\(\t\)*\(.*\)/Opcode_\1="\${Opcode_\1} \3"/g' \
# -e 's/Opcode_PENT_3DNOW/Opcode_ATHLON_3DNOW/g' -e 's/\\//g' \
# -e 's/syn match gasOpcode_\([^\t]*\)*.*\/<\(.*\)>\//Opcode_\1="\${Opcode_\1} \2"/g' \
# >> Opcode_list
#
# Modify file Opcode_list replacing all occurrences of:
# * Opcode_Base within the section "Tejas New Instructions (SSSE3)" with Opcode_SSSE3
# * Opcode_Base within the section "Willamette MMX instructions (SSE2 SIMD Integer Instructions)"
# with Opcode_WILLAMETTE_Base
# return values
EXIT_FOUND=0
EXIT_NOT_FOUND=1
EXIT_USAGE=2
# settings
InstSet_Base=""
Recursive=false
Count_Matching=false
Leading_Separator='\s'
Trailing_Separator='(\s|$)' # $ matches end of line for non-parametric instructions like nop
Case_Insensitive=false
Invert=false
Verbose=false
Stop_After=0
Line_Numbers=false
Leading_Context=0
Trailing_Context=0
source Opcode_list # include opcodes from a separate file
# GAS-specific opcodes (unofficial names) belonging to the x64 instruction set.
# They are generated by GNU tools (e.g. GDB, objdump) and specify a variant of ordinal opcodes like NOP and MOV.
# If you do not want these opcodes to be recognized by this script, comment out the following line.
Opcode_X64_GAS="nopw nopl movabs"
# instruction sets
InstSet_X86="8086_Base 186_Base 286_Base 386_Base 486_Base PENT_Base P6_Base KATMAI_Base WILLAMETTE_Base PENTM_Base"
InstSet_IA64="IA64_Base"
InstSet_X64="PRESCOTT_Base X64_Base X86_64_Base NEHALEM_Base X64_GAS"
InstSet_MMX="PENT_MMX KATMAI_MMX X64_MMX"
InstSet_MMX2="KATMAI_MMX2"
InstSet_3DNOW="ATHLON_3DNOW"
InstSet_SSE="KATMAI_SSE P6_SSE X64_SSE"
InstSet_SSE2="SSE2 X64_SSE2"
InstSet_SSE3="PRESCOTT_SSE3"
InstSet_SSSE3="SSSE3"
InstSet_VMX="VMX X64_VMX"
InstSet_SSE4_1="SSE41 X64_SSE41"
InstSet_SSE4_2="SSE42 X64_SSE42"
InstSet_SSE4A="AMD_SSE4A"
InstSet_SSE5="AMD_SSE5"
InstSet_FMA="FUTURE_FMA"
InstSet_AVX="SANDYBRIDGE_AVX"
InstSetDep_X64="X86"
InstSetDep_MMX2="MMX"
InstSetDep_SSE2="SSE"
InstSetDep_SSE3="SSE2"
InstSetDep_SSSE3="SSE3"
InstSetDep_SSE4_1="SSSE3"
InstSetDep_SSE4_2="SSE4_1"
InstSetDep_SSE4A="SSE3"
InstSetDep_SSE5="FMA AVX" # FIXME not reliable
InstSetList="X86 IA64 X64 MMX MMX2 3DNOW SSE SSE2 SSE3 SSSE3 VMX SSE4_1 SSE4_2 SSE4A SSE5 FMA AVX"
# architectures
Arch_8086="8086_Base"
Arch_186="186_Base"
Arch_286="286_Base"
Arch_386="386_Base"
Arch_486="486_Base"
Arch_Pentium="PENT_Base PENT_MMX" # Pentium = P5 architecture
Arch_Athlon="ATHLON_3DNOW"
Arch_Deschutes="P6_Base P6_SSE" # Pentium II
Arch_Katmai="KATMAI_Base KATMAI_MMX KATMAI_MMX2 KATMAI_SSE" # Pentium III
Arch_Willamette="WILLAMETTE_Base SSE2" # original Pentium IV (x86)
Arch_PentiumM="PENTM_Base"
Arch_Prescott="PRESCOTT_Base X64_Base X86_64_Base X64_SSE2 PRESCOTT_SSE3 VMX X64_VMX X64_GAS" # later Pentium IV (x64) with SSE3 (Willamette only implemented SSE2 instructions) and VT (VT-x, aka VMX)
Arch_P6=""
Arch_Barcelona="ATHLON_3DNOW AMD_SSE4A"
Arch_IA64="IA64_Base" # 64-bit Itanium RISC processor; incompatible with x64 architecture
Arch_Penryn="SSSE3 SSE41 X64_SSE41" # later (45nm) Core 2 with SSE4.1
Arch_Nehalem="NEHALEM_Base SSE42 X64_SSE42" # Core i#
Arch_SandyBridge="SANDYBRIDGE_AVX"
Arch_Haswell="FUTURE_FMA"
Arch_Bulldozer="AMD_SSE5"
ArchDep_8086=""
ArchDep_186="8086"
ArchDep_286="186"
ArchDep_386="286"
ArchDep_486="386"
ArchDep_Pentium="486"
ArchDep_Athlon="Pentium" # FIXME not reliable
ArchDep_Deschutes="Pentium"
ArchDep_Katmai="Deschutes"
ArchDep_Willamette="Katmai"
ArchDep_PentiumM="Willamette" # FIXME Pentium M is a Pentium III modification (with SSE2). Does it support also WILLAMETTE_Base instructions?
ArchDep_Prescott="Willamette"
ArchDep_P6="Prescott" # P6 started with Pentium Pro; FIXME Pentium Pro did not support MMX instructions (introduced again in Pentium II aka Deschutes)
ArchDep_Barcelona="Prescott" # FIXME not reliable
ArchDep_IA64=""
ArchDep_Penryn="P6"
ArchDep_Nehalem="Penryn"
ArchDep_SandyBridge="Nehalem"
ArchDep_Haswell="SandyBridge"
ArchDep_Bulldozer="Haswell" # FIXME not reliable
ArchList="8086 186 286 386 486 Pentium Athlon Deschutes Katmai Willamette PentiumM Prescott P6 Barcelona IA64 Penryn Nehalem SandyBridge Haswell Bulldozer"
Un esempio di Opcode_list
file generato e modificato utilizzando le istruzioni in opcode
a partire dal 27 ottobre 2014, è disponibile all'indirizzo http://pastebin.com/yx4rCxqs. Puoi inserire questo file direttamente in opcode
al posto del source Opcode_list
linea. Ho inserito questo codice perché Stack Exchange non mi permetteva di inviare una risposta così grande.
Infine, il resto di opcode
file con la logica attuale:
usage() {
echo "Usage: $0 OPTIONS"
echo ""
echo " -r set instruction sets recursively according to dependency tree (must precede -a or -s)"
echo " -a set architecture"
echo " -s set instruction set"
echo " -L show list of available architectures"
echo " -l show list of available instruction sets"
echo " -i show base instruction sets of current instruction set (requires -a and/or -s)"
echo " -I show instructions in current instruction set (requires -a and/or -s)"
echo " -c print number of matching instructions instead of normal output"
echo " -f find instruction set of the following instruction (regex allowed)"
echo " -d set leading opcode separator (default '$Leading_Separator')"
echo " -D set trailing opcode separator (default '$Trailing_Separator')"
echo " -C case-insensitive"
echo " -v invert the sense of matching"
echo " -V print all lines, not just the highlighted"
echo " -m stop searching after n matched instructions"
echo " -n print line numbers within the original input"
echo " -B print n instructions of leading context"
echo " -A print n instructions of trailing context"
echo " -h print this help"
echo
echo "Multiple architectures and instruction sets can be used."
echo
echo "Typical usage is:"
echo " objdump -M intel -d FILE | $0 OPTIONS"
echo " objdump -M intel -d FILE | $0 -s SSE2 -s SSE3 -V Highlight SSE2 and SSE3 within FILE."
echo " objdump -M intel -d FILE | tail -n +8 | $0 -r -a Haswell -v -m 1 Find first unknown instruction."
echo " $0 -C -f ADDSD Find which instruction set an opcode belongs to."
echo " $0 -f .*fma.* Find all matching instructions and their instruction sets."
echo
echo "The script uses Intel opcode syntax. When used in conjunction with objdump, \`-M intel' must be set in order to prevent opcode translation using AT&T syntax."
echo
echo "BE AWARE THAT THE LIST OF KNOWN INSTRUCTIONS OR INSTRUCTIONS SUPPORTED BY PARTICULAR ARCHITECTURES (ESPECIALLY AMD'S) IS ONLY TENTATIVE AND MAY CONTAIN MISTAKES!"
kill -TRAP $TOP_PID
}
list_contains() { # Returns 0 if $2 is in array $1, 1 otherwise.
local e
for e in $1; do
[ "$e" = "$2" ] && return 0
done
return 1
}
build_instruction_set() { # $1 = enum { Arch, InstSet }, $2 = architecture or instruction set as obtained using -L or -l, $3 = "architecture"/"instruction set" to be used in error message
local e
list_contains "`eval echo \\\$${1}List`" "$2" || (echo "$2 is not a valid $3."; usage) # Test if the architecture/instruction set is valid.
if [ -n "`eval echo \\\$${1}_${2}`" ]; then # Add the instruction set(s) if any.
for e in `eval echo \\\$${1}_${2}`; do # Skip duplicates.
list_contains "$InstSet_Base" $e || InstSet_Base="$e $InstSet_Base"
done
fi
if [ $Recursive = true ]; then
for a in `eval echo \\\$${1}Dep_$2`; do
build_instruction_set $1 $a "$3"
done
fi
InstSet_Base="`echo $InstSet_Base | sed 's/$ *//'`" # Remove trailing space.
}
trap "exit $EXIT_USAGE" TRAP # Allow usage() function to abort script execution.
export TOP_PID=$$ # PID of executing process.
# Parse command line arguments.
while getopts ":ra:s:LliIcf:Fd:D:CvVm:nB:A:h" o; do
case $o in
r) Recursive=true ;;
a) build_instruction_set Arch "$OPTARG" "architecture" ;;
s) build_instruction_set InstSet "$OPTARG" "instruction set" ;;
L) echo $ArchList; exit $EXIT_USAGE ;;
l) echo $InstSetList; exit $EXIT_USAGE ;;
i)
if [ -n "$InstSet_Base" ]; then
echo $InstSet_Base
exit $EXIT_USAGE
else
echo -e "No instruction set or architecture set.\n"
usage
fi
;;
I)
if [ -n "$InstSet_Base" ]; then
for s in $InstSet_Base; do
echo -ne "\e[31;1m$s:\e[0m "
eval echo "\$Opcode_$s"
done
exit $EXIT_USAGE
else
echo -e "No instruction set or architecture set.\n"
usage
fi
;;
c) Count_Matching=true ;;
f)
# Unlike architectures, instruction sets are disjoint.
Found=false
for s in $InstSetList; do
for b in `eval echo \\\$InstSet_$s`; do
Found_In_Base=false
for i in `eval echo \\\$Opcode_$b`; do
if [[ "$i" =~ ^$OPTARG$ ]]; then
$Found_In_Base || echo -ne "Instruction set \e[33;1m$s\e[0m (base instruction set \e[32;1m$b\e[0m):"
echo -ne " \e[31;1m$i\e[0m"
Found_In_Base=true
Found=true
fi
done
$Found_In_Base && echo ""
done
done
if [ $Found = false ]; then
echo -e "Operation code \e[31;1m$OPTARG\e[0m has not been found in the database of known instructions." \
"Perhaps it is translated using other than Intel syntax. If obtained from objdump, check if the \`-M intel' flag is set." \
"Be aware that the search is case sensitive by default (you may use the -C flag, otherwise only lower case opcodes are accepted)."
exit $EXIT_NOT_FOUND
else
exit $EXIT_FOUND
fi
;;
d) Leading_Separator="$OPTARG" ;;
D) Trailing_Separator="$OPTARG" ;;
C) Case_Insensitive=true ;;
v) Invert=true ;;
V) Verbose=true ;;
m) Stop_After=$OPTARG ;;
n) Line_Numbers=true ;;
B) Leading_Context=$OPTARG ;;
A) Trailing_Context=$OPTARG ;;
h) usage ;;
\?)
echo -e "Unknown option: -$OPTARG\n"
usage
;;
esac
done
shift $((OPTIND-1))
[ -n "$1" ] && echo -e "Unknown command line parameter: $1\n" && usage
[ -z "$InstSet_Base" ] && usage
# Create list of grep parameters.
Grep_Params="--color=auto -B $Leading_Context -A $Trailing_Context"
[ $Count_Matching = true ] && Grep_Params="$Grep_Params -c"
[ $Case_Insensitive = true ] && Grep_Params="$Grep_Params -i"
[ $Invert = true ] && Grep_Params="$Grep_Params -v"
[ $Stop_After -gt 0 ] && Grep_Params="$Grep_Params -m $Stop_After"
[ $Line_Numbers = true ] && Grep_Params="$Grep_Params -n"
# Build regular expression for use in grep.
RegEx=""
for s in $InstSet_Base; do
eval RegEx=\"$RegEx \$Opcode_$s\"
done
# Add leading and trailing opcode separators to prevent false positives.
RegEx="$Leading_Separator`echo $RegEx | sed "s/ /$(echo "$Trailing_Separator"|sed 's/[\/&]/\\\&/g')|$(echo "$Leading_Separator"|sed 's/[\/&]/\\\&/g')/g"`$Trailing_Separator"
[ $Verbose = true -a $Count_Matching = false ] && RegEx="$RegEx|\$"
# The actual search.
grep $Grep_Params -E "$RegEx" && exit $EXIT_FOUND || exit $EXIT_NOT_FOUND
Tieni presente che se la tua query di ricerca è troppo grande (ad esempio, con il set di istruzioni Haswell e il -r
switch - questo include centinaia di istruzioni), il calcolo può procedere lentamente e impiegare molto tempo su input di grandi dimensioni per i quali questo semplice script non era previsto.
Per informazioni dettagliate sull'utilizzo consultare
./opcode -h
L'intero opcode
script (con Opcode_list incluso) può essere trovato su http://pastebin.com/A8bAuHAP.
Sentiti libero di migliorare lo strumento e di correggere eventuali errori che potrei aver commesso. Infine, vorrei ringraziare Jonathan Ben-Avraham per la sua fantastica idea di utilizzare il gas.vim
di Shirk file.
MODIFICA: Lo script è ora in grado di trovare a quale set di istruzioni appartiene un codice operazione (è possibile utilizzare un'espressione regolare).
Ho sbattuto fuori un programma in Rust che cerca di fare questo. Penso che funzioni, anche se non è documentato e terribilmente fragile:
https://github.com/pkgw/elfx86exts
Esempio di utilizzo:
$ cd elfx86exts
$ cargo build
[things happen]
$ cargo run -- /bin/ls
Compiling elfx86exts v0.1.0 (file:///home/peter/sw/elfx86exts)
Finished dev [unoptimized + debuginfo] target(s) in 1.9 secs
Running `target/debug/elfx86exts /bin/ls`
MODE64
CMOV
SSE2
SSE1
Per prima cosa, decompila il tuo binario:
objdump -d binary > binary.asm
Quindi trova tutto Istruzioni SSE4 nel file assembly:
awk '/[ \t](mpsadbw|phminposuw|pmulld|pmuldq|dpps|dppd|blendps|blendpd|blendvps|blendvpd|pblendvb|pblenddw|pminsb|pmaxsb|pminuw|pmaxuw|pminud|pmaxud|pminsd|pmaxsd|roundps|roundss|roundpd|roundsd|insertps|pinsrb|pinsrd|pinsrq|extractps|pextrb|pextrd|pextrw|pextrq|pmovsxbw|pmovzxbw|pmovsxbd|pmovzxbd|pmovsxbq|pmovzxbq|pmovsxwd|pmovzxwd|pmovsxwq|pmovzxwq|pmovsxdq|pmovzxdq|ptest|pcmpeqq|pcmpgtq|packusdw|pcmpestri|pcmpestrm|pcmpistri|pcmpistrm|crc32|popcnt|movntdqa|extrq|insertq|movntsd|movntss|lzcnt)[ \t]/' binary.asm
(Nota:CRC32 potrebbe corrispondere ai commenti.)
Trova le istruzioni AVX più comuni (incluso scalare, incluso AVX2, famiglia AVX-512 e alcuni FMA come vfmadd132pd
):
awk '/[ \t](vmovapd|vmulpd|vaddpd|vsubpd|vfmadd213pd|vfmadd231pd|vfmadd132pd|vmulsd|vaddsd|vmosd|vsubsd|vbroadcastss|vbroadcastsd|vblendpd|vshufpd|vroundpd|vroundsd|vxorpd|vfnmadd231pd|vfnmadd213pd|vfnmadd132pd|vandpd|vmaxpd|vmovmskpd|vcmppd|vpaddd|vbroadcastf128|vinsertf128|vextractf128|vfmsub231pd|vfmsub132pd|vfmsub213pd|vmaskmovps|vmaskmovpd|vpermilps|vpermilpd|vperm2f128|vzeroall|vzeroupper|vpbroadcastb|vpbroadcastw|vpbroadcastd|vpbroadcastq|vbroadcasti128|vinserti128|vextracti128|vpminud|vpmuludq|vgatherdpd|vgatherqpd|vgatherdps|vgatherqps|vpgatherdd|vpgatherdq|vpgatherqd|vpgatherqq|vpmaskmovd|vpmaskmovq|vpermps|vpermd|vpermpd|vpermq|vperm2i128|vpblendd|vpsllvd|vpsllvq|vpsrlvd|vpsrlvq|vpsravd|vblendmpd|vblendmps|vpblendmd|vpblendmq|vpblendmb|vpblendmw|vpcmpd|vpcmpud|vpcmpq|vpcmpuq|vpcmpb|vpcmpub|vpcmpw|vpcmpuw|vptestmd|vptestmq|vptestnmd|vptestnmq|vptestmb|vptestmw|vptestnmb|vptestnmw|vcompresspd|vcompressps|vpcompressd|vpcompressq|vexpandpd|vexpandps|vpexpandd|vpexpandq|vpermb|vpermw|vpermt2b|vpermt2w|vpermi2pd|vpermi2ps|vpermi2d|vpermi2q|vpermi2b|vpermi2w|vpermt2ps|vpermt2pd|vpermt2d|vpermt2q|vshuff32x4|vshuff64x2|vshuffi32x4|vshuffi64x2|vpmultishiftqb|vpternlogd|vpternlogq|vpmovqd|vpmovsqd|vpmovusqd|vpmovqw|vpmovsqw|vpmovusqw|vpmovqb|vpmovsqb|vpmovusqb|vpmovdw|vpmovsdw|vpmovusdw|vpmovdb|vpmovsdb|vpmovusdb|vpmovwb|vpmovswb|vpmovuswb|vcvtps2udq|vcvtpd2udq|vcvttps2udq|vcvttpd2udq|vcvtss2usi|vcvtsd2usi|vcvttss2usi|vcvttsd2usi|vcvtps2qq|vcvtpd2qq|vcvtps2uqq|vcvtpd2uqq|vcvttps2qq|vcvttpd2qq|vcvttps2uqq|vcvttpd2uqq|vcvtudq2ps|vcvtudq2pd|vcvtusi2ps|vcvtusi2pd|vcvtusi2sd|vcvtusi2ss|vcvtuqq2ps|vcvtuqq2pd|vcvtqq2pd|vcvtqq2ps|vgetexppd|vgetexpps|vgetexpsd|vgetexpss|vgetmantpd|vgetmantps|vgetmantsd|vgetmantss|vfixupimmpd|vfixupimmps|vfixupimmsd|vfixupimmss|vrcp14pd|vrcp14ps|vrcp14sd|vrcp14ss|vrndscaleps|vrndscalepd|vrndscaless|vrndscalesd|vrsqrt14pd|vrsqrt14ps|vrsqrt14sd|vrsqrt14ss|vscalefps|vscalefpd|vscalefss|vscalefsd|valignd|valignq|vdbpsadbw|vpabsq|vpmaxsq|vpmaxuq|vpminsq|vpminuq|vprold|vprolvd|vprolq|vprolvq|vprord|vprorvd|vprorq|vprorvq|vpscatterdd|vpscatterdq|vpscatterqd|vpscatterqq|vscatterdps|vscatterdpd|vscatterqps|vscatterqpd|vpconflictd|vpconflictq|vplzcntd|vplzcntq|vpbroadcastmb2q|vpbroadcastmw2d|vexp2pd|vexp2ps|vrcp28pd|vrcp28ps|vrcp28sd|vrcp28ss|vrsqrt28pd|vrsqrt28ps|vrsqrt28sd|vrsqrt28ss|vgatherpf0dps|vgatherpf0qps|vgatherpf0dpd|vgatherpf0qpd|vgatherpf1dps|vgatherpf1qps|vgatherpf1dpd|vgatherpf1qpd|vscatterpf0dps|vscatterpf0qps|vscatterpf0dpd|vscatterpf0qpd|vscatterpf1dps|vscatterpf1qps|vscatterpf1dpd|vscatterpf1qpd|vfpclassps|vfpclasspd|vfpclassss|vfpclasssd|vrangeps|vrangepd|vrangess|vrangesd|vreduceps|vreducepd|vreducess|vreducesd|vpmovm2d|vpmovm2q|vpmovm2b|vpmovm2w|vpmovd2m|vpmovq2m|vpmovb2m|vpmovw2m|vpmullq|vpmadd52luq|vpmadd52huq|v4fmaddps|v4fmaddss|v4fnmaddps|v4fnmaddss|vp4dpwssd|vp4dpwssds|vpdpbusd|vpdpbusds|vpdpwssd|vpdpwssds|vpcompressb|vpcompressw|vpexpandb|vpexpandw|vpshld|vpshldv|vpshrd|vpshrdv|vpopcntd|vpopcntq|vpopcntb|vpopcntw|vpshufbitqmb|gf2p8affineinvqb|gf2p8affineqb|gf2p8mulb|vpclmulqdq|vaesdec|vaesdeclast|vaesenc|vaesenclast)[ \t]/' binary.asm
NOTA:testato con gawk
e nawk
.