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查看:
下面通过将golang源码直接编译成go格式的汇编代码(截取一部分)如下图:
go tool compile -S print.go >> print.s
根据上图可以发现,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幅图:
第一幅图黄框处是系统调用的汇编代码,可以和golang源码汇编文件作对比看有什么区别。
第二幅图是执行syscall之前相关寄存器和内存的数据。rdi=1代表amd64架构下 系统调用号是1(可以从/usr/include/asm/unistd_64.h 处查到)。rsi=0xc0000140c0表示字符串的地址,红色箭头黄框处就是此地址指向的内存中的数据(10进制数表示),根据ascii表查找104=h 101=e....确实是"hello world\n"。
系统调用时如果调用成功,会把输出长度返回给ax
有疑问加站长微信联系(非本文作者)