SIGSEGV 使用程序集访问数组元素时
SIGSEGV When accessing array element using assembly
背景:
我是组装新手。当我学习编程时,我制作了一个程序,可以实现高达 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 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 并用 edx
or
。一个 32 位整数是 4 个字节,所以[edx*4+base_address]
.将结果移动到eax
,然后将eax
放入int answer
(我猜这是在堆栈-0x14
(。我真的看不出有什么大问题。
反汇编编译器的.exe
:
当我用普通C++(answer = tables [factor1 << 10 | factor2];
(替换asm
块并将其反汇编时,这就是我在英特尔语法中得到的:
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)
我不太熟悉英特尔语法,所以我只是尝试理解AT&T语法:
它首先将tables
数组的基址移动到 %eax
中。然后,将第一个参数移动到%edx
中。它将%edx
向左移动 10,然后使用第二个参数将其or
。然后,通过将%edx
向左移动 2,它实际上将%edx
乘以 4。然后,它将其添加到%eax
(数组的基址(。所以,基本上它只是这样做:[edx*4+0x47e018]
(英特尔语法(或0x47e018(,%edx,4)
AT&T。它将它进入%eax
的元素的值移动到 int answer
.这种方法更"扩展",但它的作用与我的手写程序集相同!那么,为什么我在编译器工作正常的情况下给出SIGSEGV
呢?
我敢打赌(从反汇编中(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) )
这是对 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
- 如何访问宏中定义的数组元素
- 使用指针访问数组元素时出现意外结果
- CUDA C/C 访问先前的数组元素
- 访问边界外的数组元素会损坏它
- C++ 如何使用类方法正确访问动态数组元素
- 通过指针访问数组元素
- 提高访问大型数组元素的性能
- 在C 中,可以用语法[i,j,..]实现访问数组元素的功能,而语法就像numpy中的ndarray样式一样
- 如何在C 中的2D动态数组中访问1个元素
- 从C++中的指针访问数组元素
- 访问数组元素的不同方式
- 如何访问指针的数组元素
- 使用C++中的指针访问多维数组元素
- 从私有数组类C++访问类对象元素
- 设置数组元素时MSVC访问冲突
- 使用多态基类(包含虚函数)访问数组元素时的类型
- 如何使用指针访问第N-d数组元素
- 在 c++ 中使用数组参数访问单个元素的字符
- 通过名称访问数组元素
- 从char*访问数组元素