如何从汇编程序指令转到C代码

问题描述 投票:5回答:2

我有一个任务,除其他外,我需要在.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 assembly instructions instruction-set
2个回答
3
投票

该架构是TriCore(如果这有任何区别)。

当然。汇编代码始终是特定于体系结构的。

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

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

例如,TriCore的Tasking编译器有时甚至会为两个不同的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代码。

不幸的是,这需要很长时间 - 但我不知道任何更快的方法。


3
投票

我应该修补现有的指令集测试,它不测试所有使用的指令。所以我需要查看一级代码的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,如果它将一个零位插入一个位域,可能很容易就是and~1(0xFE),假设TriCore具有和即时。如果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然后读取该变量将是另一种阻止常量传播的方法,即使对于需要在没有外部输入的情况下运行的测试,如果这比使用无线函数更容易。 (编译器在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在指定的插入位置将一个位从一个寄存器复制到另一个寄存器,并以寄存器和直接源形式存在。

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

© www.soinside.com 2019 - 2024. All rights reserved.