编译器涉及的汇编知识

声明

毕生所学!术语不对,做法不恰当也没办法,毕竟只知道这么点。

基础

参考学长发的汇编入门

函数

可能上面的教程差的就是函数怎么写了
首先两个寄存器很重要ebpesp
ebp表示当前函数的栈空间的首地址
esp是当前栈顶
push的时候就会把esp减4,注意是减,不是加,栈从高地址向低地址扩展
也可以sub esp, 0x4,分配局部变量的空间的时候就是这样做的

// 一个递归的函数
fun(int v1, int v2)
{
	v1 = v1 + 1;
	if (v1 <= v2)
		v1 = fun(v1, v2);
	return v1;	
}

简化版的汇编代码,v1,v2都没有存到栈里

global main


main:
    push ebp
    mov ebp, esp
    push 7; 传入参数v2
    push 3; 传入参数v1
    ; 传参数是从右向左传
    call fun
    mov esp, ebp
    pop ebp
    ret

fun:
    ; 保存现场的操作
    push ebp ; 把上一个函数的基址寄存器存起来
    mov ebp, esp ; 把当前ebp 设为当前栈顶esp 

    ; 这里是函数内部的代码
    ; 关于局部变量,仅作示例,在fun中无含义
    sub esp, 0x8 ; 给局部变量分配0x8字节的空间, 比如int a, b;
    mov eax, [ebp-0x4] ; 访问第一个4字节的局部变量,即访问 a
    mov eax, [ebp-0x8] ; 访问第二个4字节的局部变量,即访问 b
    ; 关于访问传入的参数
    mov eax, [ebp+0x8] ; 访问最后一个push的参数,即v1
    mov ebx, [ebp+0xc] ; 访问倒数第二个push的参数,即v2    
    add eax, 1
    cmp eax, ebx
    jg out
    push ebx
    push eax
    call fun 
out:
    ; 返回值约定保存在eax上,所以不用动了
    ; 恢复现场的操作
    mov esp, ebp ; 意思就是不管你esp怎么减,或者不管push,pop了多少次,都把栈顶恢复到原来的情况
    pop ebp
    ret ; 返回

编译执行

nasm -f elf test.asm -o test.o ; gcc -m32 test.o -o test
./test ; echo $? # 执行并打印eax
./test # 执行

注意事项

操作数限制

一些指令的某些操作数必须要是寄存器或者常量
一些指令的某些操作数必须要是寄存器
一些指令必须至少要有一个寄存器的操作数
具体的我也不清楚,我都是试出来的

	mov [a], [b] ; 就不行
	
	; 就必须
	mov eax, [b]
	mov [a], eax
	

db, dw, dd

word 是2个字节
dword 是4个字节
然后dw是指word而不是dword,dd才是指dword
所以要声明全局int变量,要用dd
参考

; 声明4字节全局变量a
section .data
    a dd 0
    

idiv

有符号数除法
具体的自己搜一下这个指令吧
idiv 如果使用一个操作数的用法
idiv ebx
表示 edx: eax / ebx,结果商存到eax,余数存到edx上
edx 是高32位,eax是低32位
需要一个cdq指令把edx设置为恰当的指
如果eax为正数,则edx设为0;如果eax为负数,则edx设为0xffffffff
示例

int div_mod(int a, int b)
{
    print_int(a / b);
    print_int(a % b);
}

void main(){
    div_mod(7, 3);
}

extern printf
global main

div_mod:
    push ebp
    mov ebp, esp
    mov eax, [ebp+0x8]
    mov ebx, [ebp+0xc]
    cdq ; 给edx 设置恰当的值,所以需要的话要先把edx的值存起来
    idiv ebx
    push eax ; 传入商 
    call print_int ; 打印
    mov eax, [ebp+0x8]
    mov ebx, [ebp+0xc]
    cdq
    idiv ebx
    push edx ; 传入余数
    call print_int ; 打印
    mov esp, ebp
    pop ebp
    ret

main:
    push ebp
    mov ebp, esp
    push 3
    push 7
    call div_mod
    mov esp, ebp
    pop ebp
    ret


print_int:
    push ebp
    mov ebp, esp
    mov eax, [ebp + 0x8]
    push 0x0a6425
    mov ebx, esp
    push eax
    push ebx
    call printf 
    mov esp, ebp
    pop ebp
    ret


调用外部函数

使用printf实现的print_int


extern printf
global main:

print_int:
    push ebp
    mov ebp, esp
    mov eax, [ebp + 0x8]
    push 0x0a6425 ; 这个其实就是“%d\n”,不想用全局变量了
    mov ebx, esp
    push eax
    push ebx
    call printf 
    mov esp, ebp
    pop ebp
    ret


main:
    push ebp
    mov ebp, esp
    push 3
    call print_int
    mov esp, ebp
    pop ebp
    ret

这个函数用printf可以打印四个寄存器的值


extern printf
global main:
print:
    push ebp
    mov ebp, esp
    push edx
    push ecx
    push ebx
    push eax
    push format
    call printf  
    pop eax
    pop eax
    pop ebx
    pop ecx
    pop edx
    mov esp, ebp
    pop ebp
    ret

main:
    push ebp
    mov ebp, esp
    mov eax, 1
    mov ebx, 2
    mov ecx, 3
    mov edx, 4
    call print
    call print
    mov esp, ebp
    pop ebp
    ret
section .data
	format db 'eax:%d ebx:%d ecx:%d edx:%d', 0xa, 0 ; 0xa是‘\n’, 0是‘\0’

总结

写一下总结吧

看起来汇编的东西也就只有这么点。
前面的抽象语法树的设计还比较完善,后面用起来还是比较好用的。
没有生成中间代码,就直接输出汇编代码了。
没有中间代码,就没法优化,不过直接写汇编这么底层的东西,各种功能想要实现还是都可以实现的。虽然还有一些bug,但是不管了。
主要的精力还是花在设计上了,设计就差不多得凭空弄出一个东西来。最后还是自顶向下大法好,自顶向下过一遍,就发现了需要哪些功能,凭空就弄出一个操作数语义的类来,接口一设计一实现,后面就是利用这些接口的事情了。再根据出现的bug修修补补,就差不多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值