当我试图了解GCC优化过程并找出在此过程中使用或未使用哪些指令时,遇到了同样的问题。由于我对大量的操作代码不满意,因此我一直在寻找一种方法来可视化反汇编代码中的特定(例如SSE3)指令,或者至少打印一些最小的统计信息,例如这些指令是否存在以及有多少?在二进制文件中。
我还没有找到任何现有的解决方案,但是Jonathan Ben-Avraham的答案非常有用,因为它指出了一个很好的(甚至是部分结构化的)操作代码源。基于此数据,我编写了一个Bash脚本,该脚本可以可视化特定的指令集或grep
在从中馈入输出时使用这些指令集打印统计信息objdump
。
操作代码列表已转换为独立的Bash脚本,然后将其包含在主文件中(出于更好的可读性)(我已将其简单命名为)opcode
。由于gas.vim
(根据乔纳森的回答,Shirk的vim
语法定义中的)操作码是根据不同的CPU体系结构进行系统分组的(貌似),因此,我尝试保留这种划分并进行体系结构->指令集映射;我现在不确定这是否是个好主意。映射不准确,我什至必须对原始图像进行一些更改gas.vim
分组。由于与体系结构相关的指令集不是我的初衷,因此我仅尝试构建Internet上描述的主要体系结构的指令集,而没有咨询制造商的文档。AMD架构对我来说似乎一点也不可靠(除了3DNow!和SSE5之类的指令集)。但是,我决定将各种体系结构的指令集的代码留在这里,以供其他人检查和更正/改进并为其他人提供一些初步的结果。
主文件开头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"
可以在http://pastebin.com/yx4rCxqs上找到按照2014年10月27日Opcode_list
的说明生成和修改的文件的示例。您可以将此文件直接插入该行。我之所以把这段代码放出来是因为Stack Exchange不允许我发送这么大的答案。opcode
opcode
source Opcode_list
最后,其余opcode
文件具有实际逻辑:
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
请注意,如果您的搜索查询过大(例如,使用Haswell指令集和-r
开关-其中包含数百条指令),则可能会缓慢进行计算,并且在大型输入上进行此操作会花费很长时间,而这个简单的脚本本来不打算这样做。
有关使用的详细信息,请咨询
./opcode -h
整个opcode
脚本(包括Opcode_list)可以在http://pastebin.com/A8bAuHAP找到。
随时改进工具并纠正我可能犯的任何错误。最后,我要感谢乔纳森·本·阿夫拉罕(Jonathan Ben-Avraham)使用Shirk gas.vim
文件的好主意。
编辑:脚本现在能够找到操作码所属的指令集(可以使用正则表达式)。