C 语言内嵌汇编栈指针问题

C语言内嵌汇编栈错误详解及解决
本文深入探讨了C语言内嵌汇编中栈错误的原因及解决方法,详细分析了esp指针操作不当导致的段错误,并通过实例展示了如何避免此类问题。

今天在学习 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 偏移的间接寻址方式,当在内嵌汇编中更改了 esp 后,寻址发生错误,所以肯定不能输出我们想要的结果了。那为什么会发生 `Segmentation fault` 呢?其原因在于,自己分配的栈空间实际上还是放置于栈中的,而在 A 处的汇编代码将 esp 指针往栈底方向进行了偏移,而 stack 相对于 esp 的偏移是一个固定值,导致了访问 stack 第一个元素的时候就已经来到了程序栈空间之外,从而触发了 `Segmentation fault`。

在更简单的情景中也可能因为 esp 和 ebp 的移动引起程序工作的异常。例如下面这段简单的代码:

/* test2.c */
#include <stdio.h>

int main(void)
{
	int x = 2;

	asm volatile (
		"pushl %%esp\n\t"
		:
	);

	printf("%X", x);

	return 0;
}



某一次运行的时候输出结果是这样的:  804845B

这个错误的来源也是因为压栈导致了 esp 指针的移动,从而在通过间接寻址时查找 x 的时候发生了异常。找到的不是 x ,而是我们刚刚压入的 esp 寄存器的部分字节。

因此,一个好习惯是:在 C 语言中内嵌汇编时,如果需要移动栈指针,必须在结束汇编代码前将栈指针恢复到内嵌汇编前的状态。

转载于:https://my.oschina.net/siyuany/blog/385299

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值