今天在学习 C 语言内嵌汇编的实验过程中,发现内嵌汇编极容易造成段错误。经过分析,我发现错误源于对于栈指针 esp 的不当操作导致的,现将整个问题记录如下。
实验需求是,在内存中分配一段空间用作函数调用栈。代码如下:
/* test.c */
#include <stdio.h>
#define STACK_SIZE 1024 // 1K
int main(void)
{
unsigned char stack[STACK_SIZE];
int i;
// A. Initialize stack
asm volatile (
"pushl %%ebp\n\t" // store ebp
"movl %%esp, %%ebp\n\t" // temp esp
"movl %0, %%esp\n\t" // new esp
"pushl %%ebp\n\t" // store old esp
"movl %%esp, %%ebp\n\t" // new ebp
:
: "m" (stack)
);
// Any codes here
// Check the status of the stack
for (i = 0; i < STACK_SIZE; i++)
{
printf("%02X ", stack[i]);
}
printf("\n");
// Restore system-allocated stack
asm volatile (
"movl %%ebp, %%esp\n\t" // clear stack
"popl %%ebp\n\t" // obtain old esp
"movl %%ebp, %%esp\n\t" // restore esp
"popl %%ebp\n\t" // restore ebp
:
:
);
return 0;
}
这段代码可以通过编译,然而运行的时候总是出现 `Segmentation fault`。没办法,我只能首先用 `gcc -S test.c -o test.s -m32` 查看生成的汇编代码。这里给出中间部分输出栈内容部分的汇编代码:
#NO_APP
movl $0, 24(%esp)
jmp .L2
.L3:
leal 28(%esp), %edx
movl 24(%esp), %eax
addl %edx, %eax
movzbl (%eax), %eax
movzbl %al, %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
addl $1, 24(%esp)
.L2:
cmpl $1023, 24(%esp)
jle .L3
movl $10, (%esp)
call putchar
在更简单的情景中也可能因为 esp 和 ebp 的移动引起程序工作的异常。例如下面这段简单的代码:
/* test2.c */
#include <stdio.h>
int main(void)
{
int x = 2;
asm volatile (
"pushl %%esp\n\t"
:
);
printf("%X", x);
return 0;
}
这个错误的来源也是因为压栈导致了 esp 指针的移动,从而在通过间接寻址时查找 x 的时候发生了异常。找到的不是 x ,而是我们刚刚压入的 esp 寄存器的部分字节。
因此,一个好习惯是:在 C 语言中内嵌汇编时,如果需要移动栈指针,必须在结束汇编代码前将栈指针恢复到内嵌汇编前的状态。