ATT汇编

本篇博文为学习《深入理解计算机系统》过程中写的笔记,其中一些图片也摘自本书


首先,概述一下GCC编译器驱动程序把高级语言转换为机器语言的总过程。( 预处理器,编译器,汇编器,连接器的概念和区别

在这里插入图片描述
本篇文章针对其中.s文件即汇编程序进行学习。在学习ATT汇编之前,推荐先去了解一下计算机组成原理这门课(这里是我的学习霸笔记)

一、得到汇编文本的具体操作

使用命令gcc -Og -S hello.c,就能得到hello.s,其中编译选项-Og告诉编译器使用会生成符合原始C代码整体结构的机器代码的优化等级
在这里插入图片描述
删除以“.”开头的附加项后得到纯净的汇编代码
在这里插入图片描述
(默认64位应用程序,也可以在后面加上-m32指定编译为32位应用程序)

二、ATT汇编代码

具体可阅《深入理解计算机系统》(CSAPP)读书笔记
例题:

void swap(int *a,int *b)
{
	int temp=*a;
	*a=*b;
	*b=temp;
}
int main()
{
	int array[10]={10,9,8,7,6,5,4,3,2,1};
	for(int i=0;i<10;i++)
	{
		for(int j=0;j+1<10;j++)
		{
			if(array[j]>array[j+1])
			{
			 swap(&array[j],&array[j+1]);
			}
		}
	}
	for(int i=0;i<10;i++)
	{
		printf("%d",array[i]);
	}
	return 0;
}

对应ATT汇编格式

swap:
	movl (%rdi), %eax		# 将a指针指向的值加载到寄存器eax中
	movl (%rsi), %edx		# 将b指针指向的值加载到寄存器edx中
	movl %edx, (%rdi)		# 将edx中的值存储到a指针指向的位置
	movl %eax, (%rsi)		# 将eax中的值存储到b指针指向的位置
	ret				# 返回

main:
	pushq %rbp			# 保存调用者的栈帧指针
	pushq %rbx			# 保存调用者的寄存器
	subq $56, %rsp			# 分配56字节的局部变量空间
	movl $10, (%rsp)		# 初始化数组元素,这里的数据是int类型的,用编码后缀为“l”来表示4字节整形
	movl $9, 4(%rsp)
	movl $8, 8(%rsp)
	movl $7, 12(%rsp)
	movl $6, 16(%rsp)
	movl $5, 20(%rsp)
	movl $4, 24(%rsp)
	movl $3, 28(%rsp)
	movl $2, 32(%rsp)
	movl $1, 36(%rsp)
	movl $0, %ebp			# 初始化外层循环计数器ebp
	jmp .L3				# 跳转到外层循环
.L4:
	movl %ebx, %eax			# 将内层循环计数器ebx的值复制到eax
	addl $1, %eax           #内层循环计数器加1
.L6:     # 补全的内容在这!!!
    cmpl $8, %eax          # 比较eax和8
    jg .L12                # 如果eax大于8,跳转到.L12结束内层循环
    cmpl (%rsp,%rax,4), (%rsp,%rax,4)   # 比较array[j]和array[j+1]
    je .L4                 # 如果相等,跳转到.L4继续内层循环
    leaq (%rsp,%rax,4), %rdi            # 将array[j]的地址存储到rdi,用leaq是因为这里传的是地址而不是数据
    leaq 4(%rsp,%rax,4), %rsi           # 将array[j+1]的地址存储到rsi
    call swap              # 调用swap函数交换array[j]和array[j+1]的值
    jmp .L4                # 继续内层循环
.L12:
	addl $1, %ebp			# 外层循环计数器ebp加1
.L3:
	cmpl $9, %ebp			# 比较ebp和9,这里ebp代表i
	jg .L13				# 如果ebp > 9,则跳转到.L13
	movl $0, %eax			# 将eax置零,这里eax表示j
	jmp .L6				# 跳转到.L6,继续内层循环
.L13:
	movl $0, %ebx			# 初始化内层循环计数器ebx
	jmp .L7				# 跳转到.L7
.L8:
	movslq %ebx, %rax			# 将数组索引ebx扩展为64位存储在rax中
	movl (%rsp,%rax,4), %esi		# 将数组索引为ebx的元素加载到esi中
	leaq .LC0(%rip), %rdi		# 将格式字符串地址加载到rdi中
	movl $0, %eax			# 将eax置零
	call printf@PLT			# 调用printf函数输出结果
	addl $1, %ebx			# 内层循环计数器ebx加1
.L7:
	cmpl $9, %ebx			# 比较ebx和9
	jle .L8				# 如果ebx <= 9,则跳转到.L8,继续循环
	movq 40(%rsp), %rax		# 恢复栈帧指针
	subq %fs:40, %rax		# 检查栈是否被破坏
	jne .L14				# 如果栈被破坏,则跳转到.L14
	movl $0, %eax			# 返回0
	addq $56, %rsp			# 释放局部变量空间
	popq %rbx			# 恢复调用者的寄存器
	popq %rbp			# 恢复调用者的栈帧指针
	ret				# 返回

用C语言goto版本描述上述汇编过程

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int arr[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    int i, j;
    i = 0;
    goto L3; // 跳转到外层循环起始位置
L3:
    if (i > 9) // 外层循环结束条件
        goto L13; // 跳转到循环结束后的位置
    j = 0;
    goto L6; // 跳转到内层循环起始位置
L6:
    if (j > 8) // 内层循环结束条件
        goto L4; // 跳转到内层循环结束后的位置
    swap(&arr[j], &arr[j + 1]); // 调用swap函数进行交换
    j++;
    goto L6; // 跳转回内层循环起始位置
L4:
    i++;
    goto L3; // 跳转回外层循环起始位置
L13:
    for (j = 0; j <= 9; j++) {
        printf("%d ", arr[j]);
    }
    printf("\n");
    return 0;
}
  • main后为什么需要 pushq %rbp 和 %rbx?
    1、寄存器%rbp和%rbx被划分为被调用者寄存器,用于保存上个函数的基址指针和非易失性寄存器的值。简而言之,保存%rbp和%rbx寄存器的值目的是为了保护这些寄存器的值,防止在函数执行期间被修改
    2、pushq %rbp将rbp寄存器的值压入栈中。这是为了保存调用者函数的栈底指针,以便在函数结束时恢复调用者的栈帧。
    3、push %rbx 将rbx寄存器的值压入栈中。这是为了保存rbx寄存器的值,以便在函数结束时恢复rbx的原始值。rbx寄存器在函数内部可能会被使用和修改,所以在函数结束时需要恢复原值。
    4、在函数结束时,通过popq指令可以将之前保存在栈中的%rbp和%rbx的值恢复回来,以确保函数结束后栈帧的正确恢复。(.L7中体现)

  • ATT汇编中,过程调用的本质和约定?
    1、栈帧:过程调用时,使用栈来储存相关的数据,如函数的局部变量、参数值、返回地址(像push %rbppop %rbp)等信息,创建栈一般是在函数开头有个像本例题中subq $56, %rsp movl $10, (%rsp)…的操作,销毁时再addq $56, %rsp加回去,释放空间
    2、调用和返回:用call指令先将返回地址压入栈,并跳转到过程的入口。在返回过程中ret,从栈中弹出返回地址,并跳转回调用处的下一条指令(下图讲得更清楚)
    ebp:(base pointer )可称为“帧指针”或“基址指针”
    esp:(stack pointer)可称为“ 栈指针”
    在这里插入图片描述
    图片出处
    3、参数传递和返回值:传参对应的寄存器分别为:第一个参数%rdi,第二个参数%rsi…返回值一般在%rax

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值