我有一个任务,除其他外,我需要在.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;
该架构是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代码。
不幸的是,这需要很长时间 - 但我不知道任何更快的方法。
我应该修补现有的指令集测试,它不测试所有使用的指令。所以我需要查看一级代码的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 found,insert
在指定的插入位置将一个位从一个寄存器复制到另一个寄存器,并以寄存器和直接源形式存在。
替换整个字节可能只是一个存储而不是读/修改/写。所以这里的关键是结构定义,而不仅仅是编译到指令的语句。