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

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

内容简介: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是如何系统调用的》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

计算机算法导引

计算机算法导引

卢开澄 / 第2版 (2006年1月1日) / 2006-1 / 38.0

本书为《计算机算法导引——设计与分析》的第2版。书中内容分3部分:第1部分是基本算法,按方法论区分,包含优先策略与分治策略、动态规划、概率算法、并行算法、搜索法、数据结构等;第2部分是若干专题,包括排序算法、计算几何及计算数论、线性规划;第3部分是复杂性理论与智能型算法,其中,智能型算法主要介绍了遗传算法和模拟退火算法。本书可作为计算机系本科学生及研究生教材,数学系师生和科研T作者也可将其作为参考......一起来看看 《计算机算法导引》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具