【问题标题】:Is it possible to find a list of all of the assembly instructions that GCC can generate?是否可以找到 GCC 可以生成的所有汇编指令的列表?
【发布时间】:2018-08-07 14:06:16
【问题描述】:

他在OpenSecurityTraining 主持的Xeno Kovah's Introduction to x86 Assembly 第一天的作业中分配,

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

没有 推送/弹出 来电/回电 MOV/LEA 添加/订阅 JMP/JCC CMP/测试 与/或/异或/非 SHR/SHL IMUL/DIV REP STOS,REP MOV 离开

编写一个程序来找到我们没有涉及的指令,并报告 明天指导。

他进一步预测分配,

  • 稍后将介绍的不计入的说明:SAL/SAR
  • 跳跃变化或IMUL/DIVMUL/IDIV 变体也不算数
  • 其他禁止使用的指令:任何浮点数(因为我们不涉及此类中的内容。)
  • 他在视频中说不能使用内联汇编。 (在被问到时提及)。

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

这个问题的基础似乎是实际使用的指令的一小部分需要知道才能进行逆向工程(这是课程的重点)。 Xeno 似乎正试图找到一种有趣的、有启发性的方式来说明这一点,

我认为了解 20-30(不包括变化)就足够了,您将很少会检查手册

虽然我欢迎大家加入我在 OpenSecurityTraining 的这个很棒的课程中,但问题是关于我提出的从 GCC 中找出它的方法(如果可能的话)。不是,让人们真正完成 Xeno 的任务。 ;)

【问题讨论】:

  • 非常有趣的问题!
  • 那么,objdump,并为您在文本文件中找到的内容创建一个库/散列?比较英特尔的 x86 主文档,看看遗漏了什么。 Objdump 输出足够正常,您应该能够很容易地进行字符串解析。
  • 编写一个程序来查找我们未涉及的指令...“查找”指令到底是什么意思?您不会真正“编写程序来查找指令”。他们的意思是“找到我们没有涵盖的指令”并“使用”该指令编写程序吗?在这种情况下,只需用汇编语言编写即可。试图找到一个使用gcc 生成此类指令的 C 程序听起来并不高效。
  • @lurker 是的,就是这个意思。用汇编语言写作超出了作业的范围。这是一门专注于逆向工程的课程,请参阅我的更新。
  • xlat 或者可能是hlt 会想到我认为很少见的指令。 aaa 现在也很少见了,因为这是 70 年代风格的作品。

标签: gcc assembly x86 instructions


【解决方案1】:

不是 objdumping 随机可执行文件并审核它们然后创建源代码,是否可以找到 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
  • cdq (before idiv)
  • jcxz/jecxz(在 gcc 中很少见,在 VC++ 中有些常见)
  • setCC
  • cmpxchg(在同步代码中);
  • cmovCC
  • adc(在 32 位代码中进行 64 位运算时)
  • int3(通常在函数边界上发出,通常作为填充物)
  • 其他一些字符串指令 (scas/cmps),尤其是旧编译器上的固定序列

然后就是 SSE & co 的整个世界......

【讨论】:

  • gcc 不会发出inc(除了可能带有-Os)。它总是add dst,1,即使对于使用-march=skylake 的注册目的地,它也应该告诉它您不关心Silvermont/KNL 或Pentium 4 (where inc is slower),但是gcc 的调整选项并没有得到很好的维护。具有讽刺意味的是,clang 使用 inc 没有调整选项,但使用 add reg,1-march=skylake. /facepalm。
  • 我认为 gcc 也不会发出 jecxz / jrcxz。它不像loop那么慢,但我认为gcc不知道如何通过分支而不更新标志来优化adc循环。 (一般来说,它只知道如何将adc 用于__int128(或int64_t 在32 位机器上),而不是任意精度)
  • 我很确定我在调试和逆向的时候看到了很多inc,但必须说(1)它并不总是gcc代码,以及(2)他们中的许多人可能是lock inc...
  • 嗯,在某些情况下可能会使用inc;我在 Arch Linux 上的 /bin/bash 的反汇编中看到了一些,我认为元数据表明它是用 gcc6.3.1 编译的。 IDK 使用了哪些选项。
  • jecxz 是我在对 VC++ 2010 (IIRC) 二进制文件进行逆向工程时发现的;我清楚地记得看到过其中的一些,我确实很惊讶,因为我从未在 gcc 代码中看到过它们。 adc:我一直在处理 int64_t 的 32 位二进制文​​件中看到它。
【解决方案2】:

这个问题的基础似乎是实际使用的指令的一小部分需要知道才能进行逆向工程

是的,这通常是正确的。有些指令 gcc 永远不会发出,like enter(因为它在现代 CPU 上比push rbp / mov rbp, rsp / sub rsp, some_constant很多)。

xlatloop 等其他旧的/晦涩的东西也将不会被使用 because they aren't faster,而 gcc 的 -Os 不会在不关心性能的情况下全力优化大小。 (clang -Oz 更具侵略性,但如果有人愿意教它loop 指令,请 IDK。)

当然 gcc 永远不会发出像 wrmsr 这样的特权指令。对于一些非“正常”的非特权指令like rdtsccpuid 有内在函数(__builtin_... 函数)。


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

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

请参阅gcc-internals documentation for them


解决此问题的另一种方法是查看 x86 指令参考手册(例如 this HTML extract,并查看 标签 wiki 中的其他链接)并查找您拥有的链接还没见过。然后编写一个 gcc 认为有用的函数。

例如如果你还没有看到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 中产生结果(使用复制并添加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 可以识别为旋转的 shift/OR。

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


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

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

【讨论】:

    猜你喜欢
    • 2013-07-25
    • 2011-05-09
    • 2018-04-28
    • 2011-04-10
    • 2022-07-11
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多