汇编对sp指针进行修改_Golang学习Plan9汇编学习

 看书犯困,那是梦开始的地方。

Plan9汇编语言选择的是Plan9操作系统的汇编器支持的汇编语言

Plan9操作系统是大名鼎鼎的贝尔实验室设计开发的,虽然它具有很多的优点,但由于诸多原因,它并不是一款成功的操作系统,可能生不逢时吧

Golang的开发团队和Plan9操作系统团队基本一致,Golang选择了Plan9汇编就在情理之中了

函数声明

看下面函数:

TEXT ·Print(SB), NOSPLIT, $16-16
    MOVQ strp+0(FP), AX
    MOVQ AX, 0(SP)
    MOVQ size+8(FP), BX
    MOVQ BX, 8(SP)
    CALL ·Println(SB)
    RET
  1. TEXT是函数声明,类似func
  2. 奇怪的符号·,就是.的含义
  3. Print(SB)中Print是函数名
  4. NOSPLIT使编译器不要进行内联优化
  5. $16-16表示这个函数栈帧大小是16,有16个字节的参数数据处于caller栈帧中

全局变量声明

DATA  msg<>+0x00(SB)/8, $"Hello, W"  // 初始化变量。可以参考本文中SB寄存器的含义
DATA  msg<>+0x08(SB)/8, $"orld!\n"
GLOBL msg<>(SB),NOPTR,$16  // 将上面的变量声明为 global,后面需要跟两个参数,flag和变量的大小

8(SP)含义

8(SP)是指栈顶所指的地址+8之后的地址,也就是栈顶第二个元素

其他的比如0(CX)也是类似的含义,括号前面表示偏移,负数则表示负向偏移

汇编中不允许调用内建函数

汇编中不允许调用内建函数。但是有替代方案

bin/main/main.go

package main

import _ "fmt"

func Print(delta string)

func main() {
 Print("hello")
}

bin/main/asm.s

#include "textflag.h"

TEXT ·Print(SB), NOSPLIT, $8
    CALL fmt·Println(SB)
    RET

运行上面代码会报错:main.Print: relocation target fmt.Println not defined for ABI0 (but is defined for ABIInternal)

可以改成下面方式

bin/main/main.go

package main

import (
 "fmt"
)

func Print(str string)

func main() {
 Print("hello")
}

func Println(str string) {
 fmt.Println(str)
}

bin/main/asm.s

#include "textflag.h"

TEXT ·Print(SB), NOSPLIT, $16-16
    MOVQ strp+0(FP), AX
    MOVQ AX, 0(SP)  // 第一个参数:数据的开始指针
    MOVQ size+8(FP), BX
    MOVQ BX, 8(SP)  // 第二个参数:string的大小。改成 MOVQ $100, 8(SP) 试试,会发现打印了其他地方的数据
    CALL ·Println(SB)
    RET
// 这里一定要有换行,否则编译报错

bin/main/asm.s中传递参数可能有些疑惑,main.Println函数明明只有一个参数,为什么放了两个参数?

实际上,在golang中,struct结构不被认为是一个数据单元,struct中有几个成员,就有几个数据单元,因此struct传递时就会传递几个参数

而golang中的string其实是一个struct,定义如下:

type stringStruct struct {
 str unsafe.Pointer  // 数据的开始指针
 len int  // string的大小
}

所以上面需要传递两个参数

实例

获取Goroutine Id

Goroutine Id跟线程id一样,其实是有的,只是Golang开发人员没有暴露出来,是故意为之的,避免开发人员滥用

但是,如果真的有需求,可以通过汇编取到,看代码

bin/main/main.go

package main

import (
 "fmt"
)

func getgid() int64

func main() {
 fmt.Println(getgid())
}

bin/main/asm.s

#include "textflag.h"

TEXT ·getgid(SB), NOSPLIT, $0-8
    MOVL    TLS, CX   // 取出TLS
    MOVQ    0(CX)(TLS*1), AX  // 取出当前g
    MOVQ    AX, ret+0(FP)  // 返回g结构的第一个元素,正是goroutine id
    RET

原子锁

bin/main/main.go

package main

import "fmt"

func Xadd(addr *int32, delta int32) int32

func main() {
 var a int32 = 0
 fmt.Println(Xadd(&a, 1))
 fmt.Println(a)
}

bin/main/asm.s

#include "textflag.h"

TEXT ·Xadd(SB), NOSPLIT, $0-20
 MOVQ ptr+0(FP), BX        // 第一个参数放到BX,是个指针
 MOVL delta+8(FP), AX      // 第二个参数放到AX
 MOVL AX, CX               // 第二个参数又放到CX
 LOCK                       // 锁总线,多CPU排他执行指令。下一个指令将被锁住,执行完自动释放(CPU特性)
 XADDL AX, 0(BX)            // 交换并相加。指针中的值与AX的值交换,然后两者相加,结果放到指针指向的值中
 ADDL CX, AX               // 指针中原来的值加上第二个参数,结果放入AX
 MOVL AX, ret+16(FP)       // 返回AX
 RET

常用汇编指令

  • PUSH:进栈指令,PUSH指令执行时会先将ESP减4,接着将内容写入ESP指向的栈内存。
  • POP :出栈指令,POP指令执行时先将ESP指向的栈内存的一个字长的内容读出,接着将ESP加4。注意:用PUSH指令和POP指令时只能按字访问栈,不能按字节访问栈。
  • CALL:调用函数指令,将返回地址(call指令的下一条指令)压栈,接着跳转到函数入口。
  • RET:返回指令,将栈顶返回地址弹出到EIP,接着根据EIP继续执行。
  • LEAVE:等价于 mov esp,ebp; pop ebp;
  • MOVL:在内存与寄存器、寄存器与寄存器之间转移值
  • LEAL:用来将一个内存地址直接赋给目的操作数 注意:8位指令后缀是B、16位是S、32位是L、64位是Q
  • ADD:ADDL CX, AX,保存在 AX 和 CX 寄存器中的值进行相加,然后再保存进 AX 寄存器中
  • XADD:交换并相加

常见寄存器含义

  • BP: 栈帧(函数的栈叫栈帧)的开始位置
  • SP: 栈帧的结束位置
  • PC: 就是IP寄存器,存放CPU下一个执行指令的位置地址
  • TLS: 表示的是thread-local storage,它存放了g结构体
  • FP: 用来标识函数参数、返回值。其通过symbol+offset(FP)的方式进行使用。例如arg0+0(FP)表示函数第一个参数其实的位置(amd64平台),arg1+8(FP)表示函数参数偏移8byte的另一个参数。arg0/arg1用于助记,但是必须存在,否则无法通过编译。至于这两个参数是输入参数还是返回值,得对应其函数声明的函数个数、位置才能知道
  • SB: 可以认为是内存的开始位置。foo(SB)表示foo是SB偏移0处的地址,与foo+0(SB)一个意思。变量名后面加个<>,foo<>+0x00(SB)/4则表示&foo~&foo+0x04一段区间。DATA divtab<>+0x00(SB)/4, $0xf4f8fcff就是声明一个4字节常量

参考文档

Plan9操作系统:https://plan9.io/plan9/

Plan9汇编器手册:http://doc.cat-v.org/plan_9/4th_edition/papers/asm

993a63326554e266ca557da44943e34b.png

戳↙【阅读原文

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值