【问题标题】:How to go From Assembler instruction to C code如何从汇编指令到 C 代码
【发布时间】:2018-05-31 15:43:26
【问题描述】:

我有一个任务,除其他外,我需要查看 .asm 文件以找到特定指令并“逆向工程”(找出)C 代码的哪一部分导致它在汇编器上执行等级。 (文字下方示例)

最快(最简单)的方法是什么。或者更好地说,我应该/可以注意 .asm 文件中围绕它的其他哪些命令/指令/标签,这将引导我找到正确的 C 代码?

我对汇编代码的经验几乎为零,很难弄清楚 C 代码的哪些确切行会导致特定指令发生。

架构是 TriCore,如果这有什么不同的话。

示例: 通过跟踪变量的使用位置,我设法弄清楚是什么 C 代码导致在 asm 文件中插入

 .L23:
    movh.a  a15,#@his(InsertStruct)
    ld.bu   d15,[a15]@los(InsertStruct)
    or  d15,#1
    st.b    [a15]@los(InsertStruct),d15
.L51:
    ld.bu   d15,[a15]@los(InsertStruct)
    insert  d15,d15,#0,#0,#1
    st.b    [a15]@los(InsertStruct),d15
.L17:
    mov d15,#-1

这导致我得到以下 C 代码:

InsertStruct.SomeMember = 0x1u;

InsertStruct.SomeMember = 0x0u;

【问题讨论】:

  • 这个想法是展示你对 C、编译器和汇编器的理解。有一些工具可以为您完成此操作,有时称为“反汇编程序”。然而,在大多数情况下,结果令人失望,而且有时对于经验丰富的汇编程序员来说,汇编代码的理解程度不如汇编代码。
  • 你应该完成这个练习,而不是围绕它。它的目的是提高您(缺少的)组装技能以及理解和重新实现算法的不可或缺的能力。所以:你应该注意每一行,最快的方法是了解它。
  • “哪些 C 代码行会导致特定指令发生。” - 确切的整个源代码会导致整个程序集发生。至少在优化的代码中没有直接的线对线映射,有时优化器会显着改变算法,比如通过乘法执行x / 15,或者通过直接计算结果来删除整个循环求和值等等......如果你愿意尝试从这样的程序集中重建 C 源代码,你会以完全不同的源代码结束(算法方面)。
  • 感谢您的回答,但不幸的是,这并没有帮助,我知道这是一个非常普遍的问题。不幸的是,这不是练习,而是工作。我应该修补现有的指令集测试,它不会测试所有使用的指令。所以我需要查看一级代码的 asm 文件,找出是什么 C 代码导致指令发生,这样我就可以在我的补丁中使用它。 @ Lundin,是的,考虑到 Ped7g 所说的话,很难阅读。它经过优化,一行 C 代码并不意味着使用了某条指令。可以说“相互作用”是关键。
  • 如果你想测试 ISA 中的所有指令,希望你能说服 C 编译器以某种方式生成它们是完全错误的方法。编译器的下一个版本,或在某处更改常量,可能会导致不同的代码生成。如果你需要特定的 asm,写在 asm 中。

标签: c assembly instructions instruction-set


【解决方案1】:

架构是 TriCore(如果这有什么不同的话)。

当然。汇编代码始终是特定于架构的。

... C 代码的哪一部分导致它在汇编程序级别上执行。

当使用高度优化的编译器时,您几乎没有机会:

例如,TriCore 的任务编译器有时甚至会为两个不同 C 文件中的两行不同 C 代码生成一个汇编代码片段(仅在内存中存储一​​次!)!

但是,您示例中的代码并未优化(除非您命名为 InsertStruct 的结构是 volatile)。

在这种情况下,您可以在打开调试信息的情况下编译代码并提取调试信息:从 ELF 格式文件中,您可以使用 addr2line(来自 GNU 编译器套件的免费软件)之类的工具来检查哪一行 C 代码对应于某个地址的指令。

(注意:addr2line 工具是独立于架构的,只要两个架构具有相同的宽度(32 位)、相同的字节序并且都使用 ELF 文件格式;您可以使用addr2line for ARM 来获取来自 TriCore 文件的信息。)

如果你真的需要理解一段汇编代码,我自己通常会这样做:

我启动一个文本编辑器并粘贴汇编代码:

movh.a  a15,#@his(InsertStruct)
ld.bu   d15,[a15]@los(InsertStruct)
or      d15,#1
st.b    [a15]@los(InsertStruct),d15
...

然后我用等效的伪代码替换每条指令:

a15 =  ((((unsigned)&InsertStruct)>>16)<<16;
d15 =  *(unsigned char *)(a15 + (((unsigned)&InsertStruct)&0xFFFF));
d15 |= 1;
*(unsigned char *)(a15 + (((unsigned)&InsertStruct)&0xFFFF)) = d15;
...

在下一步中,我尝试简化此代码:

a15 =  ((unsigned)&InsertStruct) & 0xFFFF0000;

然后:

d15 = *(unsigned char *)((((unsigned)&InsertStruct) & 0xFFFF0000) + (((unsigned)&InsertStruct)&0xFFFF));
...

然后:

d15 = *(unsigned char *)((unsigned)&InsertStruct);
...

然后:

d15 = *(unsigned char *)&InsertStruct;
...

最后我尝试替换跳转指令:

d15 = 0;
if(d14 == d13) goto L123;
d15 = 1;
L123:

...变成:

d15 = 0;
if(d14 != d13) d15 = 1;

...最后(也许):

d15 = (d14 != d13);

最后你在文本编辑器中有 C 代码。

不幸的是,这需要 很多 时间 - 但我不知道有什么更快的方法。

【讨论】:

  • 您使用a15 有点过于复杂了。您不会真的认为这些指令实际上是使用 C 运算符在地址周围移动,对吗?我的意思是,unsigned char *a15 = hi(&amp;InsertStruct)/unsigned d15 = a15[lo(InsertStruct)]。或者直接转到更合理的 C 语言,例如 d15 = InsertStruct.SomeMember;,一旦您了解编译器如何处理具有固定宽度指令的机器上的静态地址常量,您就会想到它。
  • @Martin Rosenau 感谢您抽出宝贵时间回答。我接受这个作为答案,考虑到你提供了两种我可以用来解决我的问题的方法。如果没有以前的汇编程序知识,我怀疑它不会有太大帮助,但总比没有好。再次感谢。
【解决方案2】:

我应该修补现有的指令集测试,它不会测试所有使用的指令。所以我需要查看一级代码的 asm 文件,找出导致指令发生的 C 代码,以便在我的补丁中使用它。

你的目标很疯狂,你问题的前半部分是倒退的/只与你的真正问题松散相关。

可能有一种方法可以说服您的编译器使用您希望它使用的每条特定指令,但它会特定于您的编译器版本、选项和所有周围代码,包括头文件中的潜在常量。

如果您想测试 ISA 中的所有指令,希望您能说服 C 编译器以某种方式生成它们是完全错误的方法。你希望你的测试在未来继续测试同样的东西,所以你应该 . 如果您需要特定的 asm,请写在 asm 中

这与几周前针对 ARM 提出的相同问题:How to force IAR to use desired Cortex-M0+ instructions (optimization will be disabled for this func.) 只是您说您将在启用优化的情况下进行构建(这可能更容易生成更广泛的指令:有些可能只可用作简单普通代码生成的窥孔优化)。


此外,从 asm 开始并将其反转为等效的 C 并不能保证编译器在编译时会选择该指令,因此问题标题仅与您的实际问题松散相关。


如果您仍然想手持编译器生成特定的 asm,创建可能只能使用非常特定的编译器/版本/选项执行您想要的脆弱源代码,第一步会想“什么时候该指令会成为优化的做事方式的一部分?”。

通常,这种思路对于通过调整源代码以提高编译效率来进行优化更有用。首先,您考虑正在编写的函数的有效 asm 实现。然后您以相同的方式编写您的 C 或 C++ 源代码,即使用您希望编译器使用的相同临时文件。例如,请参阅What is the efficient way to count set bits at a position or lower?,在那里我能够手持 gcc 使用更有效的指令序列,就像我第一次尝试时 clang 所做的那样。

有时这可以很好地工作;出于您的目的,当指令集只有一种非常好的方法来做某事时,这很简单。例如ld.bu 看起来像是一个零扩展的字节加载(u 表示无符号)到一个完整的寄存器中。 unsigned foo(unsigned char*p) {return *p;} 应该会编译成这样,你可以使用 noinline 属性来阻止它优化。

但是insert,如果这是在位域中插入一个零位,假设 TriCore 具有和-立即数,则可以很容易地成为带有 ~1 (0xFE) 的 and。如果insert 具有非立即形式,这可能是single-bit bitfield = rand() 最有效的选项(或任何在使用常量传播优化后仍不是编译时常量的值)。

对于 TriCores 的压缩算术 (SIMD) 指令,您将需要编译器自动矢量化,或使用内在函数。

在 ISA 中很可能有一些您的编译器将永远发出的指令。尽管我认为您只是在尝试测试编译器在代码的其他部分中发出的指令?你说的是“所有使用的指令”,而不是“所有的指令”,这样至少可以保证任务是可能的。


带有 arg 的非内联函数是强制为运行时变量生成代码的好方法。那些查看编译器生成的 asm 的用户经常编写小函数,这些函数接受 args 并返回一个值(或存储到全局或 volatile),以强制编译为某些东西生成代码,而不会丢弃结果,也没有常量-传播将整个函数变成return 42;,即mov-immediate / ret。有关详细信息,请参阅 How to remove "noise" from GCC/clang assembly output?,以及 Matt Godbolt 的 CppCon2017 演讲:“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”,了解有关阅读编译器生成的 asm 的一些很棒的初学者介绍,以及现代优化编译器为小型函数做了哪些事情。

分配给volatile 然后读取该变量将是另一种击败常量传播的方法,即使对于需要在没有外部输入的情况下运行的测试,如果这比使用 noinline 函数更容易的话。 (编译器每次在 C 源代码中读取时都会从 volatile 重新加载,即他们必须假设它可以被异步修改。)

int main(void) {
    volatile int vtmp = 123;
    int my_parameter = vtmp;

    ... then use my_parameter, not vtmp, so CSE and other optimizations can still work
 }

[...] 已优化

您显示的编译器输出看起来肯定没有经过优化。看起来像加载/设置位/存储,然后加载/清除位/存储,这应该已经优化为仅加载/清除位/存储。除非这些 asm 块不是真正连续的,并且您正在显示粘贴在一起的两个不同块的代码。

另外,InsertStruct.SomeMember = 0x0u; 是一个不完整的描述:它显然取决于结构定义;我假设您使用了int SomeMember :1; 一位位域成员?根据TriCore ISA ref manual I foundinsert 在指定的插入位置将一系列位从一个寄存器复制到另一个寄存器,并以寄存器和立即源形式出现。

替换整个字节可能只是存储而不是读取/修改/写入。所以这里的关键是结构定义,而不仅仅是编译成指令的语句。

【讨论】:

  • 好的,谢谢您的详细解答。你对结构是正确的。我无法在此处粘贴原始代码,所以我只是粘贴了我为重新创建插入指令和向我确认使用了插入的 asm 输出所做的操作。你也是对的,指令集测试应该只测试整个指令测试的一部分,即在一个代码级别中使用的指令,并且应该测试它们的代码部分被拼接到这是来自另一个项目,所以我应该通过测试大约 30 条使用的指令来修补它。
  • @vandelfi:我建议你聘请一位了解 asm 的顾问来看看你想通过这个测试首先解决什么问题,并帮助你弄清楚它是否即使是正确的方法。您是否正在尝试测试与不同 TriCore 芯片或其他东西的硬件兼容性?如果是这样,那么手工编写一些 asm 会更有意义。
  • @vandelfi 那么您需要直接编写专门的汇编代码来测试 CPU 故障。虽然我很难想象 CPU 仅针对特定指令发生故障,但在我天真的观点中(我不是硬件专家,我对此知之甚少)故障 CPU 可能会导致芯片的某些部分损坏,这将导致各种副作用,例如测试设置/拆卸已经受到严重影响,测试指令的结果也可能会出现偏差,但问题是是否有足够的生命来报告它。
  • @vandelfi 也仅测试指令不会捕获特定地址/数据行中的故障,即测试可能会变绿,因为它们将使用仍可访问的部分 RAM,而主代码已经存在疯了,因为它的部分 RAM 所需的地址线将出现故障。然后,您的测试代码不仅应该运行与主​​代码相同的指令,还应该验证实时代码正在积极使用的内存和其他资源,即,或者将实时数据与一些空闲空间交错用于测试,或者具有访问锁并停止主代码测试期间的代码。
  • 彼得:你知道什么是终极笑话吗?如果他们无法在汇编中创建该测试,因为由于某些标准而需要 C 并且 asm 源将不会经过某些策略检查,而将考虑根据某些指令编译的摇摇欲坠的胶带修补 C作为正确的解决方案。 :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 1970-01-01
相关资源
最近更新 更多