热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Golang源码分析】Golang如何实现自举程序入口点(五)

【Golang源码分析】Golang如何实现自举-程序入口点(五)-  根据上一章的内容得知,其实不同系统的可执行文件都有自己的格式。只要生成对应的格式后,并且有执行权限就可以执行

  根据上一章的内容得知,其实不同系统的可执行文件都有自己的格式。只要生成对应的格式后,并且有执行权限就可以执行。

  那么问题来了,所说的程序入口点到底是什么?可编译性语言,不同的语言的入口点不一样,大多数的都叫main。那么不能叫其他的吗?main真的是入口点吗?好像有很多问题需要探索,需要去挖掘。

  既然这么多问题,就带着问题来看看go1.3的入口点是什么?

1.程序入口点

  说到程序入口点,这个其实很容易理解,就是程序启动的开始地址。那么接着之前的文章中生成的demo程序,来看看程序入口点。

1.1 查看程序入口

  其实想要查看程序入口点,有很多工具比如说objdump、readelf、gdb都可以查看,但是为了解析程序入口点,还是选择使用objdump。命令如下:

#objdump -f demo


图1-1 查看程序信息

demo:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0000000000421790

  通过命令查看后,可以看到与文件的格式类型是elf64-x86-64、并且程序的入口地址是 0x0000000000421790, 也就是“start address 0x0000000000421790”这段描述,如图1-1所示。

1.2 追踪程序入口

  通过1.1小节中得知程序入口地址为 0x0000000000421790,那么可以继续使用objdump命令继续追踪下程序入口。命令如下:

#objdump --disassemble  demo |grep 421790 -C 10


图1-2 追踪程序入口

  通过命令objdump查看入口点汇编代码,过滤掉查看前后10行。如图1-2所示,程序入口是_rt0_amd64_linux,并不是main。

1.3 小节

  从追踪内容来看,其实程序入口并不是想的那样一定是main,也可以变更为其他函数。

  其实有聪明的人就会想,那么修改入口点到自己的内存地址做一定的操作在跳转到入口点做到神不知鬼不觉。其实你说到对,这个就是所谓对hook api技术,也就是函数劫持。

  不过应用级编程劫持地址并不是想的那样,如果你要从A程序去劫持到B程序是做不到的,因为应用层的程序间都是虚拟地址变更是做不到的。但是也不是完全做不到,A程序可以往B程序注入动态链接库,通过动态链接库则可以操作内存。内核级劫持则没有那么复杂,不过操作不当容易蓝屏。

2.解析程序入口源码

  已经知道入口是_rt0_amd64_linux,那么来看看到底_rt0_amd64_linux是怎么来的。

2.1 追踪_rt0_amd64_linux

  在知道入口是_rt0_amd64_linux之后,其实可以思考入口肯定是编译阶段编译进去的,但是通过上一张得知6l进行链接的时候对应的把执行结构链接起来,那么执行结构中其实就包含入口点。

  所以当要知道入口点来源于时,可以去看链接过程。

图2-1 追踪_rt0_amd64_linux

  通过图2-1可以得知,在6l编译时初始化了入口点,并且生成程序,不同的系统入口点是不一样。go对应的main函数,其实是main.main。main.main之前的其实都是一些runtime的初始化操作,比如说栈大小、内存清理、g0初始化等等。
  注意的是runtime.main其实也是一个协程,也就是说main.main也是在协程中运行的。

2.2 源码解析

  根据图2-1来解析一下对应go1.3链接过程与运行过程中的源代码。

2.2.1 libinit函数

  libinit函数在src/cmd/ld/lib.c中,libinit函数其实是链接程序时,写入程序入口点。代码如下:

void
libinit(void)
{
    char *suffix, *suffixsep;

    funcalign = FuncAlign;
    fmtinstall('i', iconv);
    fmtinstall('Y', Yconv);
    fmtinstall('Z', Zconv);
    mywhatsys();    // 获得 goroot, goarch, goos。分别代表go的root目录、go的运行系统环境、go的运行系统

    // add goroot to the end of the libdir list.
    suffix = "";
    suffixsep = "";
    if(flag_installsuffix != nil) {
        suffixsep = "_";
        suffix = flag_installsuffix;
    } else if(flag_race) {
        suffixsep = "_";
        suffix = "race";
    }
    Lflag(smprint("%s/pkg/%s_%s%s%s", goroot, goos, goarch, suffixsep, suffix));

    mayberemoveoutfile(); //如果输出文件存储则删除
    cout = create(outfile, 1, 0775); //创建输出文件
    if(cout <0) {
        diag("cannot create %s: %r", outfile);
        errorexit();
    }

    if(INITENTRY == nil) {
        INITENTRY = mal(strlen(goarch)+strlen(goos)+20);
        if(!flag_shared) {
            sprint(INITENTRY, "_rt0_%s_%s", goarch, goos); //如果不是动态链接库、则设置入口点
        } else {
            sprint(INITENTRY, "_rt0_%s_%s_lib", goarch, goos); //如果是动态链接库,则设置动态链接库入口点
        }
    }
    linklookup(ctxt, INITENTRY, 0)->type = SXREF;
}

2.2.2 main与_rt0_amd64_linux

  根据链接方法libinit得知、goarch参数等于_rt0_amd64_linux、goos参数等于linux,由于使用的是静态编译,则得出INITENTRY等于_rt0_amd64_linux,也就是对应的程序入口点。

  _rt0_amd64_linux对应的方法文件为src/pkg/runtime/rt0_linux_amd64.s,是通过汇编形式编写,如下:

#include "../../cmd/ld/textflag.h"

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
    LEAQ    8(SP), SI // argv
    MOVQ    0(SP), DI // argc
    MOVQ    $main(SB), AX    //设置main函数地址给AX变量
    JMP    AX                   //跳转到main函数

TEXT main(SB),NOSPLIT,$-8    //main函数
    MOVQ    $_rt0_go(SB), AX //设置_rt0_go函数给AX变量
    JMP    AX                   //跳转到_rt0_go函数

  根据rt0_linux_amd64.s函数得知、入口点_rt0_amd64_linux会跳转到main函数、main函数会跳转到_rt0_go函数。

2.2.3 小节

  _rt0_go函数以及runtime.main函数,就不做太多赘述。可以自行去调试验证,不过_rt0_go、_rt0_amd64_linux、main三个函数是在主线程中、并不是协程程序。调试时还需注意,然后runtime.main,main.main是在协程中,是在_rt0_go中加入runtime.main,并调用runtime·mstart函数启动M进行运行调度。

  其实除了go以外,一些其他语言程序也有自己的入口和runtime库,不过大同小异。例如c来说,则必须有libc。可以通过如下命令查看对应so:

 ldconfig -p  |grep libc.so
3.内核调用

  知道入口是怎么回事之后,还想知道知道ELF是如果在系统内调度起来的,就可以研究下内核级源码。

  用户空间ELF文件加载 函数调用栈(/fs/exec.c):

sys_execve / sys_execveat
└→ do_execve
      └→ do_execveat_common
       └→ __do_execve_file
            └→ exec_binprm
            └→ search_binary_handler
                         └→ load_elf_binary

  在函数search_binary_handler() 中,遍历系统注册的binary formats handler链表,直到找到匹配的格式。elf文件的处理函数就是load_elf_binary() 。

  如果想对内核级源码进行调试,在linux中可以使用systemtap,安装后还需要安装内核版本源码。这样就可以使用"stap -g"命令进行调试内核源码。

  可以参考之前写的一篇文章《排查API的connection reset by peer和Timeout exceeded问题》,有内核调试内容。

总结

  通过程序入口点的了解、也知道如何查看入口点。并且得知go入口的由来,并且掌握来一些调试内核技巧。

  • objdump、readelf、gdb可以查看程序入口点。
  • 64位linux下,go1.3程序入口点位_rt0_amd64_linux。
  • 6l命令中的libinit函数,用于链接go程序入口点。
  • main.main函数为go源代码中的func main函数。
  • systemtap调试内核源码前,需要安装内核源码包。
  • “stap -g”命令可用于调试内核源码。
文章贡献

crt0:
https://en.wikipedia.org/wiki...

ELF文件可执行栈的深入分析:
https://mudongliang.github.io...

如何在Linux上执行main:
https://web.archive.org/web/2...


推荐阅读
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Windows 7 部署工具DISM学习(二)添加补丁的步骤详解
    本文详细介绍了在Windows 7系统中使用部署工具DISM添加补丁的步骤。首先需要将光驱中的安装文件复制到指定文件夹,并进行挂载。然后将需要的MSU补丁解压并集成到系统中。文章给出了具体的命令和操作步骤,帮助读者完成补丁的添加过程。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 微软评估和规划(MAP)的工具包介绍及应用实验手册
    本文介绍了微软评估和规划(MAP)的工具包,该工具包是一个无代理工具,旨在简化和精简通过网络范围内的自动发现和评估IT基础设施在多个方案规划进程。工具包支持库存和使用用于SQL Server和Windows Server迁移评估,以及评估服务器的信息最广泛使用微软的技术。此外,工具包还提供了服务器虚拟化方案,以帮助识别未被充分利用的资源和硬件需要成功巩固服务器使用微软的Hyper - V技术规格。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 本文讨论了在使用Git进行版本控制时,如何提供类似CVS中自动增加版本号的功能。作者介绍了Git中的其他版本表示方式,如git describe命令,并提供了使用这些表示方式来确定文件更新情况的示例。此外,文章还介绍了启用$Id:$功能的方法,并讨论了一些开发者在使用Git时的需求和使用场景。 ... [详细]
  • 通过Anaconda安装tensorflow,并安装运行spyder编译器的完整教程
    本文提供了一个完整的教程,介绍了如何通过Anaconda安装tensorflow,并安装运行spyder编译器。文章详细介绍了安装Anaconda、创建tensorflow环境、安装GPU版本tensorflow、安装和运行Spyder编译器以及安装OpenCV等步骤。该教程适用于Windows 8操作系统,并提供了相关的网址供参考。通过本教程,读者可以轻松地安装和配置tensorflow环境,以及运行spyder编译器进行开发。 ... [详细]
  • PeopleSoft安装镜像版本及导入语言包的方法
    本文介绍了PeopleSoft安装镜像的两个版本,分别是VirtualBox虚拟机版本和NativeOS版本,并详细说明了导入语言包的方法。对于Windows版本,可以通过psdmt.exe登录进入,并使用datamover脚本导入语言包。对于Linux版本,同样可以使用命令行方式执行datamover脚本导入语言包。导入语言包后,可以实现多种语言的登录。参考文献提供了相关链接以供深入了解。 ... [详细]
author-avatar
于华521_811
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有