是否可以找到GCC可以生成的所有汇编指令的列表?

时间:2018-02-27 19:57:32

标签: gcc assembly x86 instructions

Xeno Kovah's Introduction to x86 Assembly托管的OpenSecurityTraining第一天的作业中,他指定了,

  

我们现在知道的说明(24)

     

NOP   PUSH / POP   CALL / RET   MOV / LEA   ADD / SUB   JMP / JCC   CMP / TEST   AND / OR / XOR / NOT   SHR / SHL   IMUL / DIV   REP STOS,REP MOV   LEAVE

     

编写程序以查找我们尚未涵盖的指令,并报告   明天的指导。

他进一步预测了作业,

  • 以后要涵盖的说明不计算在内:SAL / SAR
  • 跳转的变体或MUL / IDIV的{​​{1}} / IMUL变种也不计算
  • 附加禁止指示:任何浮动点(因为我们没有涵盖此类中的那些。)
  • 他在视频中说你不能使用内联汇编。 (被问到时提到)。

而不是DIV随机可执行文件并审核它们然后创建源代码,是否可以找到GCC当前输出的x86汇编指令列表?

这个问题的基础似乎是实际使用的指令子集很少,需要知道逆向工程(这是课程的重点)。 Xeno似乎试图找到一种有趣的指导方式来表达这一点,

  

我认为知道大约20-30(不计算变化)就足够了,你很快就会检查手册

虽然我欢迎大家和我一起参加OpenSecurityTraining这个很棒的课程,但问题是关于我提出的从GCC中找出它的方法(如果可能的话)。不是,人们实际上做了Xeno的任务。 ;)

2 个答案:

答案 0 :(得分:3)

  

这个问题的基础似乎是实际使用的指令子集很少,需要知道反向工程

是的,这通常是正确的。 gcc永远不会发出一些指令,like enter(因为它在现代CPU上 push rbp / mov rbp, rsp / sub rsp, some_constant慢。< / p>

xlatloop之类的其他陈旧/模糊不清的东西也将被闲置,因为它们不会更快,并且gcc的-Os不会全力以赴地优化尺寸而不关心关于表现。 (clang -Oz更具攻击性,但IDK如果有人不愿教导loop指令。)

当然,gcc永远不会发出像wrmsr这样的特权指令。对于某些无特权的说明like rdtsc__builtin_...,有一些内在函数(cpuid函数)不是“正常”。

  

是否可以找到GCC当前输出的x86汇编指令列表?

这将是gcc机器定义文件。作为可移植编译器的GCC具有自己的基于文本的语言,用于描述编译器指令集的机器定义文件。 (每个指令的作用,它可以使用的寻址模式,以及优化器可以最小化的某种“成本”。)

请参阅gcc-internals documentation for them

此问题的另一种方法是查看x86指令参考手册(例如this HTML extract,并查看标记wiki中的其他链接)并查看对于那些你还没见过的人。然后编写一个gcc会发现它有用的函数。

e.g。如果你还没有看到movsx(符号扩展名),那么写一下

long long foo(int x) { return x; }

和gcc -O3将发出(from the Godbolt compiler explorer

    movsx   rax, edi
    ret

或者在rax内获取cdqe (aka cltq in AT&T syntax)符号扩展名,强制gcc在符号扩展之前进行数学运算,因此它可以先在eax中生成结果(使用副本和-add lea)。

long long bar(unsigned x) { return (int)(x+1); }

    lea     eax, [rdi+1]
    cdqe
    ret

   # clang chooses inc edi  /  movsxd rax, edi

另见 Matt Godbolt的CppCon2017演讲:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” How to remove "noise" from GCC/clang assembly output?

让gcc发出旋转指令很有意思。 Best practices for circular shift (rotate) operations in C++。你把它写成移位/或者gcc可以识别为旋转。

因为C不提供现代CPU可以做的许多事情的标准函数(旋转,弹出,计数前导/尾随零),唯一的可移植事情是编写一个等效函数并且具有编译器识别该模式。如果你很幸运的话,gcc和clang可以在使用popcnt编译时将整个循环优化为单个-mpopcnt指令(例如,由-march=haswell启用)。如果没有,你会得到一个愚蠢的慢循环。可靠的非可移植方式是使用__builtin_popcount(),如果目标支持,则编译为popcnt指令,否则使用表查找。 _mm_popcnt_u64popcnt或者没有:如果目标不支持该指令,它就不会编译。

当然,这种方法的缺陷是它只有在您已经知道x86指令集并且任何给定指令是优化编译器的正确选择时才有效!

(以及gcc选择做什么,例如内联字符串在某些情况下对于短字符串与rep cmpsb进行比较,但我不确定这是最优的。只有rep movs / rep stos在现代CPU上有“快速字符串”支持。但我不认为gcc会使用lods或任何带有rep前缀的“字符串”指令。)

答案 1 :(得分:2)

  

是否可以找到GCC当前输出的x86汇编指令列表,而不是随机执行随机可执行文件并审核它们然后创建源代码?

您可以查看gcc使用的machine description files。在其源代码树中,查看gcc / config / i386并查看.md文件。 x86的核心是i386.md; x86的各种扩展还有其他扩展(可能包含在针对不同处理器进行优化时使用的启发式调整)。

警告:这绝对不是一个简单的阅读。

  

我认为知道大约20-30(不计算变化)就足够了,你很快就会检查手册

这是真的;根据我进行逆向工程的经验,99%的代码总是相同的东西,指令方面;比了解整个x86指令集更有用的是熟悉程序集习语,尤其是编译器经常发出的习惯用法。

话虽如此,从我的脑海中开始,一些非常常见的指令缺失(经常发出并且没有启用扩展指令集)是:

  • movzx / movsx
  • inc / dec(很少见gcc,common with VC++
  • neg
  • cdqbefore idiv
  • jcxz / jecxz(gcc很少见,VC ++有些常见)
  • setCC
  • cmpxchg(在同步代码中);
  • cmovCC
  • adc(在32位代码中进行64位运算时)
  • int3(通常在功能边界上发出,通常作为填充物发布)
  • 其他一些字符串说明(scas / cmps),特别是旧编译器上的固定序列

然后是整个SSE&amp;共...