我是装配新手。当我学习编程时,我创建了一个实现乘法表最多1000 * 1000的程序。这些表的格式是每个答案都在行factor1 << 10 | factor2
上(我知道,我知道它不是&n #39; t pretty)。然后将这些表加载到一个数组中:int* tables
。空行用0填充。Here是表格文件的链接(7.3 MB)。我知道使用汇编不会加快速度,但我只是想这么做(以及一些练习)。
我试图将此代码转换为内联汇编(tables
是全局代码):
int answer;
// ...
answer = tables [factor1 << 10 | factor2];
这就是我提出的:
asm volatile ( "shll $10, %1;"
"orl %1, %2;"
"movl _tables(,%2,4), %0;" : "=r" (answer) : "r" (factor1), "r" (factor2) );
我的C ++代码工作正常,但我的程序集失败了。 与我的C ++相比,我的程序集(特别是movl _tables(,%2,4), %0;
部分)出了什么问题
我使用了一些随机数:89 796作为factor1
和factor2
。我知道 89 << 10 | 786
处有一个元素(91922
) - 用C ++验证了这一点。当我用gdb
运行它时,我得到一个SIGSEGV:
编程接收信号SIGSEGV,分段故障。
在这一行:
"movl _tables(,%2,4), %0;" : "=r" (answer) : "r" (factor1), "r" (factor2) );
我在asm
周围添加了两种方法,这就是我知道反汇编中asm
块的位置。
asm
块:来自objdump -M att -d
的反汇编看起来很好(虽然我不确定,我对汇编很新,正如我所说):
402096: 8b 45 08 mov 0x8(%ebp),%eax
402099: 8b 55 0c mov 0xc(%ebp),%edx
40209c: c1 e0 0a shl $0xa,%eax
40209f: 09 c2 or %eax,%edx
4020a1: 8b 04 95 18 e0 47 00 mov 0x47e018(,%edx,4),%eax
4020a8: 89 45 ec mov %eax,-0x14(%ebp)
来自objdump -M intel -d
的反汇编:
402096: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
402099: 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
40209c: c1 e0 0a shl eax,0xa
40209f: 09 c2 or edx,eax
4020a1: 8b 04 95 18 e0 47 00 mov eax,DWORD PTR [edx*4+0x47e018]
4020a8: 89 45 ec mov DWORD PTR [ebp-0x14],eax
根据我的理解,它将void calc ( int factor1, int factor2 )
功能的第一个参数移动到eax
。然后它将第二个参数移动到edx
。然后它将eax
向左移动10,将or
移到edx
。 32位整数是4个字节,因此[edx*4+base_address]
。将结果移至eax
,然后将eax
放入int answer
(我猜测它位于堆栈的-0x14
)。 我真的没有看到很多问题。
.exe
:当我用普通C ++(asm
)替换answer = tables [factor1 << 10 | factor2];
块并对其进行反汇编时,这就是我在英特尔语法中得到的结果:
402096: a1 18 e0 47 00 mov eax,ds:0x47e018
40209b: 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
40209e: c1 e2 0a shl edx,0xa
4020a1: 0b 55 0c or edx,DWORD PTR [ebp+0xc]
4020a4: c1 e2 02 shl edx,0x2
4020a7: 01 d0 add eax,edx
4020a9: 8b 00 mov eax,DWORD PTR [eax]
4020ab: 89 45 ec mov DWORD PTR [ebp-0x14],eax
AT&amp; T语法:
402096: a1 18 e0 47 00 mov 0x47e018,%eax
40209b: 8b 55 08 mov 0x8(%ebp),%edx
40209e: c1 e2 0a shl $0xa,%edx
4020a1: 0b 55 0c or 0xc(%ebp),%edx
4020a4: c1 e2 02 shl $0x2,%edx
4020a7: 01 d0 add %edx,%eax
4020a9: 8b 00 mov (%eax),%eax
4020ab: 89 45 ec mov %eax,-0x14(%ebp)
我对英特尔语法并不熟悉,所以我只想尝试理解AT&amp; T语法:
首先将tables
数组的基址移动到%eax
。然后,将第一个参数移动到%edx
。它会将%edx
向左移动10,然后将or
移动到第二个参数。然后,通过将%edx
向左移动2,它实际上将%edx
乘以4.然后,它将其添加到%eax
(数组的基地址)。所以,基本上它只是这样做:[edx*4+0x47e018]
(英特尔语法)或0x47e018(,%edx,4)
AT&amp; T.它将获取的元素的值移动到%eax
并将其放入int answer
。 此方法更多&#34;扩展&#34;,但它与我手写的程序集完全相同!那么为什么我的编译工作正常时我会给SIGSEGV
?
答案 0 :(得分:2)
我打赌(从反汇编)tables
是指向数组的指针,而不是数组本身。
所以你需要:
asm volatile ( "shll $10, %1;"
movl _tables,%%eax
"orl %1, %2;"
"movl (%%eax,%2,4)",
: "=r" (answer) : "r" (factor1), "r" (factor2) : "eax" )
(不要忘记最后一行中额外的咒语)。
当然有变化,如果代码处于循环中,这可能会更有效:
asm volatile ( "shll $10, %1;"
"orl %1, %2;"
"movl (%3,%2,4)",
: "=r" (answer) : "r" (factor1), "r" (factor2), "r"(tables) )
答案 1 :(得分:2)
这是对Mats Petersson的回答的补充 - 我写这篇文章只是因为我没有立即清楚为什么OP对反汇编的分析(他的汇编和编译器生成的分析是等价的)是不正确的。
正如Mats Petersson所解释的那样,问题是tables
实际上是一个指向数组的指针,所以要访问一个元素,你必须取消引用两次。现在对我来说,在编译器生成的代码中发生 的情况并不是很明显。罪魁祸首是这条天真无邪的路线:
a1 18 e0 47 00 mov 0x47e018,%eax
对于未经训练的眼睛(包括我的眼睛),这可能看起来像值 0x47e018
被移动到eax
,但事实并非如此。相同操作码的英特尔语法表示为我们提供了线索:
a1 18 e0 47 00 mov eax,ds:0x47e018
啊 - ds:
- 所以它实际上不是一个值,而是一个地址!
对于现在想知道的任何人,以下是用于将值 0x47e018
移动到eax
的操作码和ATT语法汇编:
b8 18 e0 47 00 mov $0x47e018,%eax