Linux系统下刨析hello world背后的秘密

news2024/11/16 15:39:54
  • Hi,小伙伴们,大家好!今天给大家讲解Linux系统编程中几个重要的概念。必须理解了这几个重要概念,才能更快的入门Linux系统编程,这是不可或缺的基础知识。看似简单,如果不花一番功夫很难真正的理解它们。需要不断的对它们进行思考和理解,只有这样才能写出高可靠性的Linux程序。
  • 公人从号关注Linux兵工厂领取海量Linux学习资料

1.来自hello world的思考

  • 初学一种编程语言时,往往第一次编码时就是写一个最简单的hell world,如果不这么做好像就违背了约定俗成的传统了。我们学习Linux系统编程也不例外,只不过这次我们要刨析它背后的故事。哈哈,话不多说,直接上代码:
#include <stdio.h>
int main(void)
{
printf("Hello world!\n");
return 0;
}

linux系统上使用gcc生成可执行程序:gcc -g -W helloworld.c -o helloworld

在这里插入图片描述

整个过程看似简单,背后涉及预处理、编译、汇编和链接等多个过程。但是gcc作为一个工具集合自动完成了这些步骤。下面我们就来分析看看其中所涉及的几个步骤。

  • 预处理
    预处理用于处理预处理命令。对于上面的代码来说,唯一的预处理命令是#include。它的作用是将头文件的内容包含到本文件中。该头文件中的所有代码都会在#include处展开。可以通过gcc -E helloworld.c在预处理后自动停止后面的操作,并把预处理的结果输出到标准输出。 因此使用gcc -E helloworld.c > helloworld.i,可得到预处理后的文件。理解了预处理,就明白为什么不能在头文件中定义全局变量,这是因为定义全局变量的代码会存在于所有以#include包含该头文件的文件中,也就是说所有的这些文件,都会定义一个同样的全局变量,这样就会发生冲突。

  • 编译
    编译过程是对源代码进行语法分析,并优化产生对应的汇编代码的过程。同样,可以使用gcc也可得到汇编代码gcc -S helloworld.c -o helloworld.s。gcc的-S选项会让gcc在编译完成后而停止,这样就会产生对应的汇编文件。

  • 汇编
    汇编的过程比较简单,就是将源代码翻译成可执行的指令,并生成目标文件。对应的gcc命令为gcc -c helloworld.c -o helloworld.o

  • 链接
    链接是生成可执行程序的最后步骤,也是比较复杂的一步。它就是将各个目标文件,包括库文件链接成一个可执行程序。在这个过程中,在Linux环下,该工作是由GNU的链接器ld完成的。

在这里插入图片描述

2. helloworld可执行程序是什么文件?

  • Linux下可执行程序是二进制的,其格式一般为ELF格式

在这里插入图片描述

  • 用readelf命令查看其helloworld可执行程序的ELF格式:
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7352 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         36
  Section header string table index: 33

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400318  00000318
       000000000000003d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400356  00000356
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400360  00000360
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400380  00000380
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400398  00000398
       0000000000000030  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         00000000004003c8  000003c8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003f0  000003f0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000400420  00000420
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         0000000000400430  00000430
       0000000000000182  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005b4  000005b4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005c0  000005c0
       0000000000000011  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         00000000004005d4  000005d4
       0000000000000034  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400608  00000608
       00000000000000f4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601028  00001028
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601038  00001038
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00001038
       0000000000000035  0000000000000001  MS       0     0     1
  [28] .debug_aranges    PROGBITS         0000000000000000  0000106d
       0000000000000030  0000000000000000           0     0     1
  [29] .debug_info       PROGBITS         0000000000000000  0000109d
       0000000000000091  0000000000000000           0     0     1
  [30] .debug_abbrev     PROGBITS         0000000000000000  0000112e
       0000000000000044  0000000000000000           0     0     1
  [31] .debug_line       PROGBITS         0000000000000000  00001172
       0000000000000041  0000000000000000           0     0     1
  [32] .debug_str        PROGBITS         0000000000000000  000011b3
       00000000000000d6  0000000000000001  MS       0     0     1
  [33] .shstrtab         STRTAB           0000000000000000  00001b69
       000000000000014c  0000000000000000           0     0     1
  [34] .symtab           SYMTAB           0000000000000000  00001290
       00000000000006c0  0000000000000018          35    52     8
  [35] .strtab           STRTAB           0000000000000000  00001950
       0000000000000219  0000000000000000           0     0     1

ELF文件的主要是由各个section及symbol表组成。在上面的section列表中,比较熟悉的应该是text段、data段和bss段。

  • text段为代码段,用于保存可执行指令。
  • data段为数据段,用于保存有非0初始值的全局变量和静态变量。
  • bss段用于保存没有初始值或初值为0的全局变量和静态变量,当程序加载时,bss段中的变量会被初始化为0。

除此之外还有其他常见的段:

  • debug段:用于保存调试信息,如果不使用-g选项,则不会生成。
  • dynamic段:用于保存动态链接信息。
  • fini段:用于保存进程退出时的执行程序。当进程结束时,系统会自动执行这部分代码。
  • init段:用于保存进程启动时的执行程序。当进程启动时,系统会自动执行这部分代码。
  • rodata段:用于保存只读数据,如const修饰的全局变量、字符串常量。
  • symtab段:用于保存符号表。

3.helloworld是如何在系统上运行的?

  • 当我们在Linux系统运行helloworld时,它是如何运行的。或者说./hellworld都经历了那些操作过程。下面在Ubuntu环境下,可以使用strace跟踪系统调用,从而可以帮助我们研究系统程序加载、 运行和退出的过程。
strace ./helloworld

execve("./helloworld", ["./helloworld"], [/* 70 vars */]) = 0
brk(NULL)                               = 0x25ab000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=114859, ...}) = 0
mmap(NULL, 114859, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbb541f8000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb541f7000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fbb53c26000
mprotect(0x7fbb53de6000, 2097152, PROT_NONE) = 0
mmap(0x7fbb53fe6000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7fbb53fe6000
mmap(0x7fbb53fec000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fbb53fec000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb541f6000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb541f5000
arch_prctl(ARCH_SET_FS, 0x7fbb541f6700) = 0
mprotect(0x7fbb53fe6000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fbb54215000, 4096, PROT_READ) = 0
munmap(0x7fbb541f8000, 114859)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
brk(NULL)                               = 0x25ab000
brk(0x25cc000)                          = 0x25cc000
write(1, "Hello world!\n", 13Hello world!
)          = 13
exit_group(0)                           = ?
+++ exited with 0 +++

在Linux系统上, 当我们执行命令时,首先是由shell调用fork,然后在子进程中来执行这个命令。strace是helloworld开始执行后的输出。首先是调用execve来加载helloworld,然后ld会分别检查ld.so.nohwcap和ld.so.preload。其中,如果ld.so.nohwcap存在,则ld会加载其中未优化版本的库。如果ld.so.preload存在,则ld会加载其中的库。之后利用mmap将ld.so.cache映射到内存中,ld.so.cache中保存了库的路径,这样就完成了所有的准备工作。然后ld加载c库open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC),利用mmap及mprotect设置程序的各个内存区域,到这里,程序运行的环境已经完成。后面的write会向文件描述符1(即标准输出)输出Hello world!,返回值为13,它表示write成功的字符数。最后调用exit_group退出程序,参数为0,说明程序退出的状态。

4.总结

至此,一个简单的helloworld从编码到产生可执行程序,再到运行,背后设计的‘故事’就将完了。看似简单的一个helloword,没想到背后竟然隐藏着这么秘密,与其说秘密不如说是涉及了这么多东西。因此,在学习Linux系统编程时,我们不仅要知其然,更要知其所以然,只有这样才能深刻的理解Linux系统编程,才能在以后遇到问题时更快的分析问题。好了,这篇就先到这里吧,我们后续章节继续。加油,热爱技术的你!

一个简单的helloworld从编码到产生可执行程序,再到运行,背后设计的‘故事’就将完了。看似简单的一个helloword,没想到背后竟然隐藏着这么秘密,与其说秘密不如说是涉及了这么多东西。因此,在学习Linux系统编程时,我们不仅要知其然,更要知其所以然,只有这样才能深刻的理解Linux系统编程,才能在以后遇到问题时更快的分析问题。好了,这篇就先到这里吧,我们后续章节继续。加油,热爱技术的你!

  • 公人从号关注Linux兵工厂,获取更多硬核文章,并有海量Linux学习资料赠送哦。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

113.(leaflet之家)leaflet根据距离截取线段

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>

AAC ADTS格式分析与提取aac音频文件实战

1.AAC⾳频格式简介 AAC⾳频格式&#xff1a;Advanced Audio Coding(⾼级⾳频解码)&#xff0c;是⼀种由MPEG-4 标准定义的有损⾳频压缩格式&#xff0c;由Fraunhofer发展&#xff0c;Dolby, Sony和AT&T是主 要的贡献者。 AAC 包含两种格式&#xff1a;ADIF和ADTS 2.ADIF…

Python基础(二十):面向对象基础

文章目录 面向对象基础 一、理解面向对象 二、类和对象 1、理解类和对象

已知Retangle2的中心点坐标,角度,半长,半宽,计算四个顶点坐标和四个线段中点坐标

Halcon可以通过最小外接矩型算子获取Retangle2的中心点坐标&#xff0c;角度&#xff0c;半长&#xff0c;半宽。但是却没有四个顶点坐标和四个线段中点坐标。下面的代码是获取四个顶点坐标和四个线段中点坐标的方法。 gen_rectangle2 (Rectangle, 300, 200, 0.39, 100, 50) C…

E5071C网络分析仪

18320918653 E5071C 安捷伦E5071CAgilent 8.5G网络分析仪二手E5071C 频率范围&#xff1a;300 kHz至8.5GHz 在测试端口处保持125 dB动态范围&#xff08;典型值&#xff09; 宽动态范围&#xff1a;在测试端口上的动态范围> 123 dB&#xff08;典型值&#xff09; 极快的…

GDB调试技巧实战--找到打开某个文件的地方

问题&#xff1a; 本系列期望用一个简单的实例来展示一些调试技巧。不时更新。 本期问题是&#xff1a;如何才能快速定位到打开某个文件的代码&#xff1f;&#xff08;对应open/fopen, 读者可举一反三到其它系统函数&#xff09;。当面对屎山代码时&#xff0c;GDB的条件断点…

基于R的Bilibili视频数据建模及分析——变量相关性分析篇

基于R的Bilibili视频数据建模及分析——变量相关性分析篇 文章目录基于R的Bilibili视频数据建模及分析——变量相关性分析篇0、写在前面1、数据分析1.1 变量相关性分析1.2 单元数据直观展示1.3 多元数据直观展示2、多元数据与回归分析2.1 简单相关分析2.2 简单线性回归分析2.3 …

从0到1搭建大数据平台之调度系统

大家好&#xff0c;我是脚丫先生 (o^^o) 大数据平台核心之一在于数据计算&#xff0c;分为离线计算和实时计算任务。 然而任务是离不开调度的。比如&#xff1a;我们要进行定时抽取业务数据库 的数据&#xff0c;定时跑hive/spark任务&#xff0c;定时推送日报、月报指标数据…

数字档案馆系统测试前准备工作(“指标表”和“具体要求”可下载)

数字档案馆系统测试是国家档案局组织开展的一项针对县级以上国家综合档案馆数字档案馆建设情况的绩效评价工作。数字档案馆系统测试的评分依据是《数字档案馆系统测试指标表》&#xff08;后台回复“指标表”可下载&#xff09;&#xff0c;测试采用百分制。测试结果达到80分以…

华脉智联电力行业技术解决方案

一、前言 所谓电力应急&#xff0c;就是快速处理突发紧急事件尽量减少因供电中断造成的损失&#xff0c;快速修复故障设备或线路&#xff0c;使电力生产和运营恢复正常。重大施工事故、公共紧急事件、性质恶劣的违章操作、蓄意破坏、自然灾害等&#xff0c;都极易引发大规模停…

论文浅尝 | Future Event Prediction Based on Temporal KG Embedding

笔记整理&#xff1a;杨露露&#xff0c;天津大学硕士链接&#xff1a;https://www.techscience.com/csse/v44n3/49146/html动机对未来事件的准确预测在许多领域为社会带来巨大利益&#xff0c;减少损失&#xff0c;如内乱、流行病和犯罪。知识图谱是描述和建模复杂系统的通用语…

【状态估计】将变压器和LSTM与卡尔曼滤波器结合到EM算法中进行状态估计(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

讨教大学|六西格玛之属性值数据一致性分析

相信大家在日常的工作中肯定会遇到这样一种情况&#xff1a;产品的质量特性属于外观特性&#xff0c;即 需要靠人工判断产品是不是符合标准。当遇到客户投诉&#xff0c;或者内部报废率比较高的时候&#xff0c;经 常会遇到客户和领导的挑战&#xff1a; 如何保证员工清楚的知…

IO、NIO、BIO傻傻分不清吗,让我对象告诉你~~

1、Stream 与 Channel stream 不会自动缓冲数据&#xff0c;channel 会利用系统提供的发送缓冲区、接收缓冲区&#xff08;更为底层&#xff09;stream 仅支持阻塞 API&#xff0c;channel 同时支持阻塞、非阻塞 API&#xff0c;网络 channel 可配合 selector 实现多路复用二者…

电商系统概要设计

设计一个最小化的电商系统&#xff0c;理清楚电商系统的架构。目前唯一能确定的是&#xff0c;老板要做一个电商系统。具体做成什么样&#xff0c;还不清楚。你需要和老板讨论需求。 你&#xff1a;“咱们要做的业务模式是C2C、B2C还是B2B呢&#xff1f;” 老板&#xff1a;“…

CES 2023:NVIDIA发力元宇宙与汽车市场

作为全球规模最大、影响最为广泛的国际消费电子展&#xff0c;2023年的CES&#xff08;Consumer Electronics Show&#xff09;在美国拉斯维加斯如约而至。众所周知&#xff0c;作为全球消费电子产业发展的风向标&#xff0c;每年的CES都汇聚了各大科技企业最先进的技术与产品。…

Renesas setting-文件导入

导入工程 1.1, 文件\从文件系统选中打开项目… 1.2, 打开xpg_app文件 链接文件 2.1&#xff0c;选中项目\右键\属性 2.2&#xff0c;删除文件&#xff08;在不同电脑中绝对路径不同&#xff09; 属性\路径和符号&#xff0c; 源位置&#xff08;delete C 文件&#xff09;&…

在未联网的计算机中部署 yum 源和 EPICS 环境

1. 基本背景 EPICS 软件仓库&#xff1a;192.168.206.234:8888 安装方式&#xff1a;yum 2. 配置跳板机 跳板机功能&#xff1a;借助ssh 隧道服务进行端口转发&#xff0c;使未联网的计算机能够访问软件仓库。 将未联网的计算机与一台申请了无线校园网的笔记本电脑进行有线…

PCB结构和谐振(一)

本文构建了包括不同的旋转角度、不同的叠层和两种不同布线方法&#xff08;W 形和蛇形线&#xff09;的测试手段&#xff0c;对应于这些设计的谐振是通过 VNA 测量获得的。然后&#xff0c;使用仿真方法来帮助我们理解这些实验结果。为了消除仿真与实验之间的差异&#xff0c;研…

【chatGPT】一位聊天永远秒回的朋友

目录一、chatGPT介绍二、通过猿如意体验三、在openAI官网体验2.1注册openAI2.2使用和问题说明一、chatGPT介绍 ChatGPT是由人工智能研究实验室OpenAI在2022年11月30日发布的全新聊天机器人模型&#xff0c;一款人工智能技术驱动的自然语言处理工具。它能够通过学习和理解人类的…