从汇编角度看fmt.Println是如何系统调用的

栏目: Go · 发布时间: 5年前

内容简介:fmt.Println是打印到终端字符串,那就涉及到golang的系统调用了,系统调用的底层是通过汇编的系统调用来完成的,下面就去看一下具体的调用过程。以下代码环境:1,Linux version 3.10.0-957.12.2.el7.x86_64

fmt.Println是打印到终端字符串,那就涉及到golang的系统调用了,系统调用的底层是通过汇编的系统调用来完成的,下面就去看一下具体的调用过程。

以下代码环境:

1,Linux version 3.10.0-957.12.2.el7.x86_64

2,go version go1.12.5 linux/amd64

#main

package main
import "fmt"
func main() {
    fmt.Println("hello world!")
}

跟踪函数调用发现最终会调用Syscall函数,见下方代码:

#syscall/syscall_unix.go

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)

这4个函数没有函数体,它们的是通过汇编实现的,其中Syscall是这里Println的底层函数,其参数总共有4个,trap表示系统调用标识号(linux amd64中为1),a1表示标准输出(Stdout = 1)标识号,a2表示字符串内存地址,a3表示字符串长度。以下是汇编实现:

#syscall/asm_linux_amd64.s

TEXT    ·Syscall(SB),NOSPLIT,$0-56
#1,  通知runtime调度器让出运行时间
    CALL    runtime·entersyscall(SB)

 #2,   获取内存中的调用参数并按约定传递给寄存器)
    MOVQ    a1+8(FP), DI
    MOVQ    a2+16(FP), SI
    MOVQ    a3+24(FP), DX
    MOVQ    $0, R10
    MOVQ    $0, R8
    MOVQ    $0, R9
    MOVQ    trap+0(FP), AX  // syscall entry

#3,通知内核系统(amd64)调用
    SYSCALL

#4,判断系统调用的执行结果,并进行跳转到ok处
    CMPQ    AX, $0xfffffffffffff001
    JLS ok

#5-1,执行失败,置空返回值
    MOVQ    $-1, r1+32(FP)
    MOVQ    $0, r2+40(FP)
    NEGQ    AX
    MOVQ    AX, err+48(FP)
  
#6-1,恢复goroutine的运行并返回
    CALL    runtime·exitsyscall(SB)
    RET

 #5-2,执行成功,拷贝执行结果到返回值
ok:
    MOVQ    AX, r1+32(FP)
    MOVQ    DX, r2+40(FP)
    MOVQ    $0, err+48(FP)

#6-2,恢复goroutine的运行并返回
    CALL    runtime·exitsyscall(SB)
    RET

以下是系统调用sys_write的说明:

///usr/src/kernels/3.10.0-957.12.2.el7.x86_64/include/linux/syscalls.h
//fd标准输入输出,buf输出字符串地址,count字符串长度
asmlinkage long sys_write(unsigned int fd, const char __user *buf,size_t count);

或者通过man 2 write查看:

从汇编角度看fmt.Println是如何系统调用的

图片.png

下面通过将golang源码直接编译成 go 格式的汇编代码(截取一部分)如下图:

go tool compile -S print.go >> print.s
从汇编角度看fmt.Println是如何系统调用的

BAB7B8A2-9736-424C-8DCC-93D73E882CDE.png

根据上图可以发现,CALL fmt.Fprintln(SB)之前会把标准输出地址和字符串地址压如栈中为后续获取这2个参数做准备。然后用gdb调试 工具 去去查看一下fmt.Fprintln(SB)的具体实现。

1,go build -o print 生成可执行文件

2,gdb print

3,b main.main 设置断点

4, layout split 显示源代码和反汇编窗口

5,r,s 运行并分步执行查看调用过程及寄存器,内存等信息。

最终见下2幅图:

从汇编角度看fmt.Println是如何系统调用的

D5ACDD6A-125B-4D46-B15B-57C5BA72D2A7.png

从汇编角度看fmt.Println是如何系统调用的

85374CB5-0B1D-4700-8C05-B024FA205E36.png

第一幅图黄框处是系统调用的汇编代码,可以和golang源码汇编文件作对比看有什么区别。

第二幅图是执行syscall之前相关寄存器和内存的数据。rdi=1代表amd64架构下 系统调用号是1(可以从/usr/include/asm/unistd_64.h 处查到)。rsi=0xc0000140c0表示字符串的地址,红色箭头黄框处就是此地址指向的内存中的数据(10进制数表示),根据ascii表查找104=h 101=e....确实是"hello world\n"。

系统调用时如果调用成功,会把输出长度返回给ax

从汇编角度看fmt.Println是如何系统调用的

EB425192-CB39-467A-A6A2-6765BA6EFFE4.png

从汇编角度看fmt.Println是如何系统调用的

FC717847-FA38-4747-A7C5-2537EB819F17.png


以上所述就是小编给大家介绍的《从汇编角度看fmt.Println是如何系统调用的》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

PHP实战

PHP实战

Dagfinn Reiersol、Marcus Baker、Chris Shiflett / 张颖 等、段大为 审校 / 人民邮电出版社 / 2010-01 / 69.00元

“对于那些想要在PHP方面更进一步的开发者而言,此书必不可少。” ——Gabriel Malkas, Developpez.com “简而言之,这是我所读过的关于面向对象编程和PHP最好的图书。……强烈推荐此书,绝不要错过!” ——Amazon评论 “此书是理论与实践的完美融合,到目前为止,其他任何图书都无法与它相媲美。如果5颗星是满分,它完全值得10颗星!” ——A......一起来看看 《PHP实战》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试