Golang源码分析 | 程序引导过程

环境说明

CentOS Linux release 7.2 (Final)
go version go1.16.3 linux/amd64
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7

使用gdb查看程序入口

编写一个简单的go程序

// main.go
package mainfunc main() {print("Hello world")
}
编译go build -gcflags "-N -l" -o simple main.go
使用gdb查看entry
gdb simple
(gdb) info files
Symbols from "/data/project/windeal/golang/simple/simple".
Local exec file:`/data/project/windeal/golang/simple/simple', file type elf64-x86-64.Entry point: 0x45cd800x0000000000401000 - 0x000000000045ecb6 is .text0x000000000045f000 - 0x000000000048bdb5 is .rodata0x000000000048bf40 - 0x000000000048c3e0 is .typelink0x000000000048c3e0 - 0x000000000048c3e8 is .itablink0x000000000048c3e8 - 0x000000000048c3e8 is .gosymtab0x000000000048c400 - 0x00000000004c7b68 is .gopclntab0x00000000004c8000 - 0x00000000004c8020 is .go.buildinfo0x00000000004c8020 - 0x00000000004c9240 is .noptrdata0x00000000004c9240 - 0x00000000004cb3f0 is .data0x00000000004cb400 - 0x00000000004f86b0 is .bss0x00000000004f86c0 - 0x00000000004fd990 is .noptrbss0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid
(gdb) 

可以看到程序的Entry point为 0x45cd80, 对应分段的地址范围,可以算出来程序0x45cd80在.text段。
添加断点,可以看到 Entry point: 0x45cd80 对应的内容

(gdb) b *0x45cd80
Breakpoint 1 at 0x45cd80: file /data/opt/go/src/runtime/rt0_linux_amd64.s, line 8.
(gdb) 

可以得出这个go程序的入口在 file /data/opt/go/src/runtime/rt0_linux_amd64.s, line 8.

在gdb中通过

  • b-设置断点,
  • run-启动程序,
  • n-逐步执行

可以看到程序的引导过程

rt0_linux_amd64.s 
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.#include "textflag.h"TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8JMP	_rt0_amd64(SB)TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0JMP	_rt0_amd64_lib(SB)

可以看到这部分没有太多内容,程序直接跳转执行到全局符号 _rt0_amd64(SB)

_rt0_amd64:_rt0_amd64

// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8MOVQ	0(SP), DI	// argcLEAQ	8(SP), SI	// argvJMP	runtime·rt0_go(SB)

这段代码把参数个数argc复制到DI寄存器。把参数值地址argv拷贝到SI寄存器。
关联知识:
我们分析的是amd64的源码,汇编指令按64bit寻址,每次操作8个字节的数据。 这里使用的汇编指令都带一个Q表示操作的是8个字节,如果是32bit则指定为MOVL、LEAL等,表示操作4个字节)
这里有个问题,就是为什么起始时0(SP)和8(SP)是argc和argv。 这里看了一些文章结合自己的理解,应该是操作系统的约定(需要进一步确认,留个坑后续补充)

_rt0_amd64:rt0_go

rt0_go 内容比较多,比较复杂, 逐段分析。

命令行参数拷贝

// asm_amd64.s// Defined as ABIInternal since it does not use the stack-based Go ABI (and
// in addition there are no calls to this entry point from Go code).
TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0// copy arguments forward on an even stackMOVQ	DI, AX		// argc,MOVQ	SI, BX		// argvSUBQ	$(4*8+7), SP		// 2args 2autoANDQ	$~15, SP	// 最后16位清0,实现16字节对齐MOVQ	AX, 16(SP)MOVQ	BX, 24(SP)// ......  

这一段代码是做命令行参数的拷贝和栈顶指针SP偏移的。

前面两行是把argc、argv拷贝到寄存器AX、BX。
然后SP指针向下移动4*8+7个字节,预留空间用来存放命令行参数

栈空间的寻址是自高地址向低地址

我们看下这个4*8+7的值是怎么来的。实际上是2*8+2*8+7
引导程序先把argc和argv下移,即第一个2*8。即最终的SP+16和SP+4,
第二个2*8字节,在这里并未填充值,它是用来后面给G0传递参数的,让G0启动向一个普通的调用一样。
SP+0和SP+8 可以在rt0_go的后面部分看到赋值

TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0......
ok:......MOVL	16(SP), AX		// copy argcMOVL	AX, 0(SP)MOVQ	24(SP), AX		// copy argvMOVQ	AX, 8(SP)......

多偏移的7字节是哪里来的,还没有搞懂。看到很多材料写的是为了后面的16字节对齐,但是如果仅仅只是为了16字节对齐,后面的ANDQ $~15, SP看起来就已经足够了。 先留个坑,后面搞懂了回来补充。

关于16字节对齐
关联知识:CPU有一组SSE指令,这些指令中出现的内存地址必须是16的倍数。
在 SUBQ $(4*8+7), SP之前,因为64bit机器的寻址是8字节为单元, SP对应的内存地址有2中可能:

  • 0x*****0: 最后一位是0,本身是16字节对齐
  • 0x*****8: 最后一位是8,不是16字节对齐。

如果是0x*****0这种情况,那么4*8本身就是16字节对齐的,不需要额外操作。单是如果是0x*****8这种情况的话,就需要做16字节对齐。

G0执行栈初步初始化

继续往下分析

TEXT runtime·rt0_go<ABIInternal>(SB),NOSPLIT,$0......// create istack out of the given (operating system) stack.// _cgo_init may update stackguard.MOVQ	$runtime·g0(SB), DI			// DI = g0LEAQ	(-64*1024+104)(SP), BX	   MOVQ	BX, g_stackguard0(DI)		// g0.stackguard0 = SP + (-64*1024+104)MOVQ	BX, g_stackguard1(DI)		// g0.stackguard1 = SP + (-64*1024+104)MOVQ	BX, (g_stack+stack_lo)(DI) // g0.stack.stack_lo = SP + (-64*1024+104)MOVQ	SP, (g_stack+stack_hi)(DI) // g0.stack.stack_hi = SP + (-64*1024+104)// find out information about the processor we're on,确定CPU处理器信息MOVL	$0, AXCPUIDMOVL	AX, SICMPL	AX, $0JE	nocpuinfo

这一部分是初始化g0的执行栈。
参考结构体g的定义:https://github.com/golang/go/blob/9baddd3f21230c55f0ad2a10f5f20579dcf0a0bb/src/runtime/runtime2.go#L404

TLS 线程本地存储

代码链接

	LEAQ	runtime·m0+m_tls(SB), DI	// DI = m0.tls, CALL	runtime·settls(SB)				 // 设置TLS, 还没完全看懂,待进一步分析// store through it, to make sure it worksget_tls(BX)MOVQ	$0x123, g(BX)MOVQ	runtime·m0+m_tls(SB), AXCMPQ	AX, $0x123				// 判断 TLS 是否设置成功JEQ 2(PC)							// 如果相等则向后跳转两条指令CALL	runtime·abort(SB)	  // 使用 INT 指令执行中断
ok:

关联g0和m0

代码链接

	// set the per-goroutine and per-mach "registers"// g0和m0是全局变量,先获取他们的地址分别存在寄存器CX和AXget_tls(BX)LEAQ	runtime·g0(SB), CXMOVQ	CX, g(BX)LEAQ	runtime·m0(SB), AX// 关联g0和m0	// save m->g0 = g0MOVQ	CX, m_g0(AX)// save m0 to g0->mMOVQ	AX, g_m(CX)

运行时检查

	CLD				// convention is D is always left clearedCALL	runtime·check(SB)

runtime·check(SB)的代码链接, check会进行各种检查,如果检查未通过,直接抛出异常,一般是编译过程发生了错误。
系统级的初始化
代码链接

	MOVL	16(SP), AX		// copy argcMOVL	AX, 0(SP)MOVQ	24(SP), AX		// copy argvMOVQ	AX, 8(SP)CALL	runtime·args(SB)	// 参数的初始化CALL	runtime·osinit(SB)	// CALL	runtime·schedinit(SB)

前面四行是做argc和argv的再一次拷贝。(这里没搞懂为什么需要做多次的参数拷贝,看到一些解释是为了让g0模拟普通goroutine调用)

后面三行是3个函数调用

runtime.args

func args() 代码链接

func args(c int32, v **byte) {argc = cargv = vsysargs(c, v)	
}

把参数存放在全局变量argc和argv中,供其他初始化函数使用。
func sysargs()的代码链接
sysargs()用于将一些内核级别的信息存放到执行栈中(是放在主调的栈中)
对这方面感兴趣的可以搜索golang linux 函数调用栈相关的内容

runtime·osinit

代码链接 osinit()

func osinit() {ncpu = getproccount()								// 获取CPU核心数physHugePageSize = getHugePageSize()	  // 获取内存物理页代销......osArchInit()	//  目前看是个空函数
}

运行时组件初始化

runtime·schedinit(SB) 开始是golang 运行时组件相关的初始化
代码链接

	CALL	runtime·schedinit(SB)

schedinit的代码链接

// The new G calls runtime·main.
func schedinit() {// 各种加锁......// raceinit must be the first call to race detector.// In particular, it must be done before mallocinit below calls racemapshadow._g_ := getg()if raceenabled {_g_.racectx, raceprocctx0 = raceinit()}sched.maxmcount = 10000// The world starts stopped.worldStopped()// 栈、内存分配器、调度器相关初始化moduledataverify()stackinit()			// 初始化执行栈mallocinit()		// 初始化内存分配器mallocfastrandinit() // must run before mcommoninitmcommoninit(_g_.m, -1)   // 初始化当前系统线程,只完成部分通用的初始化	cpuinit()       // must run before alginitalginit()       // maps must not be used before this callmodulesinit()   // provides activeModulestypelinksinit() // uses maps, activeModulesitabsinit()     // uses activeModulessigsave(&_g_.m.sigmask)initSigmask = _g_.m.sigmaskgoargs()goenvs()parsedebugvars()gcinit()// 创建 P, 通过 CPU 核心数和 GOMAXPROCS 环境变量确定 P 的数量lock(&sched.lock)sched.lastpoll = uint64(nanotime())procs := ncpuif n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {procs = n}if procresize(procs) != nil {throw("unknown runnable goroutine during bootstrap")}unlock(&sched.lock)// World is effectively started now, as P's can run.worldStarted()// For cgocheck > 1, we turn on the write barrier at all times// and check all pointer writes. We can't do this until after// procresize because the write barrier needs a P.if debug.cgocheck > 1 {writeBarrier.cgo = truewriteBarrier.enabled = truefor _, p := range allp {p.wbBuf.reset()}}if buildVersion == "" {// Condition should never trigger. This code just serves// to ensure runtime·buildVersion is kept in the resulting binary.buildVersion = "unknown"}if len(modinfo) == 1 {// Condition should never trigger. This code just serves// to ensure runtime·modinfo is kept in the resulting binary.modinfo = ""}
}

主goroutine启动

代码链接

	// create a new goroutine to start programMOVQ	$runtime·mainPC(SB), AX		// entry, 主 goroutine 入口地址runtime.mainPUSHQ	AXPUSHQ	$0			// arg sizeCALL	runtime·newproc(SB)		// 创建执行单元,创建gPOPQ	AXPOPQ	AX// start this MCALL	runtime·mstart(SB)			// 开始启动调度器的调度循环CALL	runtime·abort(SB)	// mstart should never returnRET// Prevent dead-code elimination of debugCallV1, which is// intended to be called by debuggers.MOVQ	$runtime·debugCallV1<ABIInternal>(SB), AX
RET

newproc(SB)的代码链接, newproc 会创建一个g

func newproc(siz int32, fn *funcval) {argp := add(unsafe.Pointer(&fn), sys.PtrSize)gp := getg()pc := getcallerpc()systemstack(func() {newg := newproc1(fn, argp, siz, gp, pc)_p_ := getg().m.p.ptr()runqput(_p_, newg, true)if mainStarted {wakep()}})
}

mstart()

runtime·mstart 相对比较复杂,后面新开一篇文章介绍。
主要调用链路是

mstart()==>mstart1()==>schedule()

主要功能是启动调度器,在shedule()中进行循环调度

我的公众号

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/190361.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

假冒 Skype 应用程序网络钓鱼分析

参考链接: https://slowmist.medium.com/fake-skype-app-phishing-analysis-35c1dc8bc515 背景 在Web3世界中&#xff0c;涉及假冒应用程序的网络钓鱼事件相当频繁。慢雾安全团队此前曾发表过分析此类网络钓鱼案例的文章。由于Google Play在中国无法访问&#xff0c;许多用户…

K8S知识点(十)

&#xff08;1&#xff09;Pod详解-启动命令 创建Pod&#xff0c;里面的两个容器都正常运行 &#xff08;2&#xff09;Pod详解-环境变量 &#xff08;3&#xff09;Pod详解-端口设置 &#xff08;4&#xff09;Pod详解-资源配额 修改&#xff1a;memory 不满足条件是不能正常…

Django之三板斧的使用,全局配置文件介绍,request对象方法,pycharm链接数据库,Django链接数据库,ORM的增删改查

【1】三板斧(3个方法)的使用 Httpresponse() 括号内写什么字符串&#xff0c;返回的就是什么字符串返回的是字符串 render(request&#xff0c; 静态文件 ) request是固定的静态文件是写在templates文件夹里面的&#xff0c;如&#xff0c;HTML文件 redirect( 重定向的地址 ) 重…

Hadoop原理,HDFS架构,MapReduce原理

Hadoop原理&#xff0c;HDFS架构&#xff0c;MapReduce原理 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c…

LeetCode刷题总结(一)

文章目录 前言题型排序问题动态规划 前言 本文把刷题过程中的总结记下来&#xff0c;方便未来回顾的时候继续拓展。 题型 排序问题 排序问题的解决方法有很多。对于简单算法来说&#xff0c;最重要的是记住思路&#xff1b;对于高级算法来说&#xff0c;最重要的是记住细节…

利用LangChain实现RAG

检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;结合了搜寻检索生成能力和自然语言处理架构&#xff0c;透过这个架构&#xff0c;模型可以从外部知识库搜寻相关信息&#xff0c;然后使用这些信息来生成response。要完成检索增强生成主要包含四个步骤…

2023亚太杯数学建模A题思路

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料5 最后 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 2023年第十三…

【中间件篇-Redis缓存数据库08】Redis设计、实现、redisobject对象设计、多线程、缓存淘汰算法

Redis的设计、实现 数据结构和内部编码 type命令实际返回的就是当前键的数据结构类型&#xff0c;它们分别是&#xff1a;string(字符串)hash(哈希)、list(列表)、set(集合)、zset (有序集合)&#xff0c;但这些只是Redis对外的数据结构。 实际上每种数据结构都有自己底层的…

【DP】背包问题全解

一.简介 DP&#xff08;动态规划&#xff09;背包问题是一个经典的组合优化问题&#xff0c;通常用来解决资源分配的问题&#xff0c;如货物装载、投资组合优化等。问题的核心思想是在有限的资源约束下&#xff0c;选择一组物品以最大化某种价值指标&#xff0c;通常是总价值或…

【Java 进阶篇】Java与JQuery选择器:解锁前端开发的魔法大门

在前端开发的世界中&#xff0c;选择器是我们与HTML文档进行互动的钥匙&#xff0c;而Java和JQuery则为我们提供了强大的工具&#xff0c;使得前端开发不再是一个艰深的谜题。本篇博客将围绕Java与JQuery选择器展开&#xff0c;深入解析选择器的奥秘&#xff0c;为你打开前端开…

Qt文档阅读笔记-Fetch More Example解析

Fetch More Example这个例子说明了如何在视图模型上添加记录。 这个例子由一个对话框组成&#xff0c;在Directory的输入框中&#xff0c;可输入路径信息。应用程序会载入路径信息的文件信息等。不需要按回车键就能搜索。 当有大量数据时&#xff0c;需要对视图模型进行批量增…

宝塔开心版hostcli的广告去除

首先感谢hostcli把宝塔7.6剥离了&#xff0c;直接安装我这里是缺少pyenv的包。 直接进入正题吧。 定位到页面左下方的广告位于 /www/server/panel/BTPanel/templates/default/layout.html “退出”按钮下方有条线开始去掉 去掉之前的忘了截图了&#xff0c;就这样吧&#xff…

《QT从基础到进阶·十七》QCursor鼠标的不同位置坐标获取

一些常用鼠标图形&#xff1a; 鼠标光标相对于整个电脑屏幕的位置&#xff1a;QCursor::pos() 当前光标相对于当前窗口的位置&#xff1a;this->mapFromGlobal(QCursor::pos()) void MainWindow::mouseReleaseEvent(QMouseEvent* event) {QPoint pos event->pos(); …

06-解决Spirng中的循环依赖问题

Bean的循环依赖问题 循环依赖: A对象中有B属性 , B对象中有A属性(丈夫类Husband中有Wife的引用, 妻子类Wife中有Husband的引用) toString()方法重写时直接输出wife/husband会出现递归导致的栈内存溢出错误 直接输出wife/husband会调用它们的toString()方法, 在toString()方法…

Spring的Redis客户端

如何在Spring中操作redis 在创建springboot项目的时候引入redis的依赖. 在配置文件里指定redis主机的地址和端口,此处我们配置了ssh隧道,所以连接的就是本机的8888端口. 创建一个controller类,注入操作redis的对象. 前面使用jedis,是通过jedis对象里的各种方法来操作redis的,此…

在任何机器人上实施 ROS 导航堆栈的指南

文章目录 路径规划参考 路径规划 路径规划是导航的最终目标。这允许用户向机器人给出目标姿势&#xff0c;并让它在给定的环境中自主地从当前位置导航到目标位置。这是我们迄今为止所做的一切&#xff08;地图绘制和本地化&#xff09;的汇集点。ROS 导航堆栈已经为我们完成了…

通讯协议学习之路(实践部分):SPI开发实践

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 本文…

【PG】PostgreSQL 预写日志(WAL)、checkpoint、LSN

目录 预写式日志&#xff08;WAL&#xff09; WAL概念 WAL的作用 WAL日志存放路径 WAL日志文件数量 WAL日志文件存储形式 WAL日志文件命名 WAL内容 检查点&#xff08;checkpoint&#xff09; 1 检查点概念 2 检查点作用 触发检查点 触发检查点之后数据库操作 设置合…

四入进博会,优衣库围绕科技可持续演绎“服装进化论”

11月5日&#xff0c;第六届中国国际进口博览会在上海拉开帷幕。这些年来&#xff0c;进博巨大的平台效应&#xff0c;使其成为各个行业头部品牌的秀场&#xff0c;也持续为消费者、产业链带来惊喜。 今年&#xff0c;也是全球服装界科技知名品牌——优衣库的第四次进博之旅。从…

Python爬虫爬取家纺数据并分析

因为时间的原因&#xff0c;没法写一个详细的教程&#xff0c;但是我可以提供一个基本的框架。你需要根据实际情况进行修改和扩展。以下是使用Python的requests库和BeautifulSoup库来爬取网页内容的基本步骤&#xff1a; # 导入所需的库 import requests from bs4 import Beaut…