0

背景:

我是组装新手。当我学习编程时,我编写了一个程序,它实现了高达 1000 * 1000 的乘法表。这些表被格式化,以便每个答案都在线factor1 << 10 | factor2(我知道,我知道,它不漂亮)。然后将这些表加载到数组中:int* tables. 空行用 0 填充。是表格文件的链接 (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 796factor1factor2. 我知道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. 然后它向左移动eax10 并or用 s 它edx。一个 32 位整数是 4 个字节,所以[edx*4+base_address]. 将结果移动到eax然后放入eaxint answer我猜它在-0x14堆栈上)。我真的看不出有什么问题。

编译器的反汇编.exe

当我asm用普通的 C++ ( answer = tables [factor1 << 10 | factor2];) 替换块并反汇编它时,这就是我在 Intel 语法中得到的:

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&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)

我对 Intel 语法不是很熟悉,所以我将尝试理解 AT&T 语法:

它首先将tables数组的基地址移动到%eax. 然后,将第一个参数移入%edx. 它向左移动%edx10,然后or使用第二个参数。然后,通过%edx向左移动 2,它实际上乘以%edx4。然后,它将它添加到%eax(数组的基地址)。所以,基本上它只是这样做了:([edx*4+0x47e018]英特尔语法)或0x47e018(,%edx,4)AT&T。它移动它进入的元素的值%eax并将其放入int answer. 这种方法更“扩展”,但它和我的手写组装一样!那么为什么我SIGSEGV的编译器工作正常呢?

4

2 回答 2

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) )   
于 2013-03-13T14:45:38.553 回答
2

这是对 Mats Petersson 答案的补充——我之所以写它只是因为我并不清楚为什么 OP 对反汇编的分析(他的汇编和编译器生成的反汇编是等价的)是不正确的。

正如 Mats Petersson 解释的那样,问题在于它tables实际上是一个指向数组的指针,因此要访问一个元素,您必须取消引用两次。现在对我来说,在编译器生成的代码中发生这种情况并不清楚。罪魁祸首是这条看似无辜的线:

a1 18 e0 47 00          mov    0x47e018,%eax

对于未经训练的眼睛(包括我的眼睛)来说,这可能看起来像是将 0x47e018移动到eax,但实际上并非如此。相同操作码的 Intel 语法表示为我们提供了线索:

a1 18 e0 47 00          mov    eax,ds:0x47e018

啊 - ds:- 所以它实际上不是一个值,而是一个地址!

对于现在想知道的任何人,以下是用于将 0x47e018移动到的操作码和 ATT 语法程序集eax

b8 18 e0 47 00          mov    $0x47e018,%eax
于 2013-03-13T15:42:17.020 回答