云计算百科
云计算领域专业知识百科平台

【Linux-System】【ELF加载揭秘:从磁盘到CPU的硬核旅程】流食般投喂

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

📝 博客写手专家

ELF程序加载与动态链接原理:从磁盘上的“0地址”到CPU取指执行的全链路拆解

——一堂把编译原理、文件系统、内存管理、进程加载串成线的硬核课


引言:为什么你写的C代码跑起来,远比“编译-运行”四个字复杂得多?

很多初学者以为,程序的生命周期不过是 gcc hello.c -o hello 然后 ./hello。但这堂课会把这一过程彻底拆开:你的代码是如何在磁盘上拥有“地址”的?可执行文件凭什么不是简单的“代码+数据”堆在一起?操作系统怎么知道该把哪段加载到哪里?动态库凭什么能被“共享”?以及,为什么你敲下的 main 函数,根本不是程序运行的真正起点?

这篇文章将基于课堂实录与板书,完整梳理从源码到进程、从静态链接到动态共享的每一个关键节点。我们会保留课堂上所有的实战命令(readelf、objdump、ldd)、经典示例(hello.c + code.c 链接实验)和那些容易被忽略的“零碎知识点”——包括为什么 .bss 段 Better Save Space,为什么内存管理的基本单位 4KB 决定了 Section 合并成 Segment 的必然性,以及 _start 函数在调用你的 main 之前到底偷偷干了多少脏活累活。


一、ELF文件格式:可执行程序不是“代码+数据”那么简单

1.1 四大组成部分与“双重视角”

一个 ELF(Executable and Linkable Format)可执行程序由四大块构成:

  • ELF Header:文件的“户口本”,记录整个文件的管理信息。
  • Program Header Table:给操作系统加载器看的“配送单”,描述哪些 Section 该合并成 Segment,以及加载到内存时的权限(读/写/执行)。
  • Section Header Table:给编译器和链接器看的“施工图”,描述 .text、.data、.bss 等每个数据节的属性、偏移量和大小。
  • 数据节(Section):真正存放代码、数据、符号表等内容的地方。

  • 📌下面是ELF文件格式预览图:在这里插入图片描述


    这里有一个贯穿全文的核心思想:ELF 文件存在两个视角。

    • 编译与链接视角:关心 Section Header Table。链接器把多个 .o 文件的 Section 合并,形成最终的可执行文件。
    • 执行与加载视角:关心 Program Header Table。操作系统不关心你有 30 个 Section,它只关心该把哪些东西打包成几个 Segment 塞进内存。
    1.2 Section 与 Segment:为什么要“合并”?

    课堂上老师用 readelf -S /usr/bin/ls 做了一个现场实验。输出显示 ls 命令足足有 30 个 Section(从 0 编号到 29)。但操作系统能直接把这 30 个小碎块分别加载进内存吗?

    不能。因为内存管理的基本单位是 4KB(一页)。 磁盘与内存进行 I/O 交互时,基本单位也是 4KB。如果一个 .text 只有 1KB、一个 .rodata 只有 512 字节,各自独占一页,内存会迅速被碎片撑爆。老师举了一个精确的例子:

    假设代码段实际占 4097 字节,只读数据占 512 字节。如果不合并,代码段要占两页(4096+1 各一页),只读数据独占一页,总共 3 页。但如果把 .text 和 .rodata 合并成一个 Segment,总共 4609 字节,只需 2 页 即可装下。

    这就是 Section 合并为 Segment 的根本原因:减少页面碎片,提高内存利用率,同时基于权限实现访问控制(代码只读、数据可写、堆栈可执行等权限控制)。

    用 readelf -l /usr/bin/ls 验证,原本的 30 个 Section 被合并成了 9 个 Program Header(Segment)。输出中的 Section to Segment mapping 清晰显示了 .text、.rodata 合并为只读可执行的代码段,.data、.bss 合并为可读写的数据段。

    1.3 BSS 段:Better Save Space

    课堂上对 .bss 的讲解非常生动。全局变量分两种:已初始化和未初始化。已初始化的必须记录具体值(如 int a = 10),存在 .data。但未初始化的(如 int b;),默认值全是 0。

    如果定义了 50 个未初始化的 int,难道要在磁盘上记录 50 个 0 吗?没必要。.bss 在磁盘上只记录一个总大小(比如 50),加载到内存时再一次性开辟 50×4=200 字节并清零。

    .bss 的全称被老师戏称为 Better Save Space——它节省的是磁盘空间。加载时才真正展开并初始化清零,这也是为什么未初始化全局变量天生为 0 的底层机制。

    1.4 ELF Header 的 Magic 与入口地址

    用 readelf -h 读取 /usr/bin/ls,你会看到:

    • Magic:文件开头一串固定魔数(如 7f 45 4c 46),操作系统靠它判断“这货是不是 ELF”。几乎所有二进制格式(图片、音乐、视频)开头都有 Magic,类似于文件的“身份证”。在这里插入图片描述

    • Entry point address:程序的入口地址。对于 ls,它是一个类似 0x401430 的虚拟地址。这个地址指向的不是你的 main 函数,而是 _start。后文会详细展开。


    下面是 ELF 文件的双重视角与 Section 合并为 Segment 的示意图:

    #mermaid-svg-Tleiinxoa3wUfc0M{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Tleiinxoa3wUfc0M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Tleiinxoa3wUfc0M .error-icon{fill:#552222;}#mermaid-svg-Tleiinxoa3wUfc0M .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Tleiinxoa3wUfc0M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Tleiinxoa3wUfc0M .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Tleiinxoa3wUfc0M .marker.cross{stroke:#333333;}#mermaid-svg-Tleiinxoa3wUfc0M svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Tleiinxoa3wUfc0M p{margin:0;}#mermaid-svg-Tleiinxoa3wUfc0M .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Tleiinxoa3wUfc0M .cluster-label text{fill:#333;}#mermaid-svg-Tleiinxoa3wUfc0M .cluster-label span{color:#333;}#mermaid-svg-Tleiinxoa3wUfc0M .cluster-label span p{background-color:transparent;}#mermaid-svg-Tleiinxoa3wUfc0M .label text,#mermaid-svg-Tleiinxoa3wUfc0M span{fill:#333;color:#333;}#mermaid-svg-Tleiinxoa3wUfc0M .node rect,#mermaid-svg-Tleiinxoa3wUfc0M .node circle,#mermaid-svg-Tleiinxoa3wUfc0M .node ellipse,#mermaid-svg-Tleiinxoa3wUfc0M .node polygon,#mermaid-svg-Tleiinxoa3wUfc0M .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Tleiinxoa3wUfc0M .rough-node .label text,#mermaid-svg-Tleiinxoa3wUfc0M .node .label text,#mermaid-svg-Tleiinxoa3wUfc0M .image-shape .label,#mermaid-svg-Tleiinxoa3wUfc0M .icon-shape .label{text-anchor:middle;}#mermaid-svg-Tleiinxoa3wUfc0M .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Tleiinxoa3wUfc0M .rough-node .label,#mermaid-svg-Tleiinxoa3wUfc0M .node .label,#mermaid-svg-Tleiinxoa3wUfc0M .image-shape .label,#mermaid-svg-Tleiinxoa3wUfc0M .icon-shape .label{text-align:center;}#mermaid-svg-Tleiinxoa3wUfc0M .node.clickable{cursor:pointer;}#mermaid-svg-Tleiinxoa3wUfc0M .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Tleiinxoa3wUfc0M .arrowheadPath{fill:#333333;}#mermaid-svg-Tleiinxoa3wUfc0M .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Tleiinxoa3wUfc0M .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Tleiinxoa3wUfc0M .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Tleiinxoa3wUfc0M .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Tleiinxoa3wUfc0M .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Tleiinxoa3wUfc0M .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Tleiinxoa3wUfc0M .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Tleiinxoa3wUfc0M .cluster text{fill:#333;}#mermaid-svg-Tleiinxoa3wUfc0M .cluster span{color:#333;}#mermaid-svg-Tleiinxoa3wUfc0M div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Tleiinxoa3wUfc0M .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Tleiinxoa3wUfc0M rect.text{fill:none;stroke-width:0;}#mermaid-svg-Tleiinxoa3wUfc0M .icon-shape,#mermaid-svg-Tleiinxoa3wUfc0M .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Tleiinxoa3wUfc0M .icon-shape p,#mermaid-svg-Tleiinxoa3wUfc0M .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Tleiinxoa3wUfc0M .icon-shape .label rect,#mermaid-svg-Tleiinxoa3wUfc0M .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Tleiinxoa3wUfc0M .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Tleiinxoa3wUfc0M .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Tleiinxoa3wUfc0M :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    合并 Section

    按权限映射

    内存中的 Segment

    代码段(R E).text + .rodata 合并

    数据段(RW).data + .bss 合并

    执行与加载视角(Program Header Table)

    操作系统按 Segment 加载

    编译与链接视角(Section Header Table)

    链接器合并 Section

    磁盘上的 ELF 文件

    30 个 Section.text / .rodata / .data / .bss …

    图解: 磁盘上的 30 个 Section 通过两个视角被处理——链接器按 Section 合并,操作系统按 Segment 加载,最终在内存中形成只读的代码段与可读写的数据段。

    二、静态链接:符号解析与地址重定位

    静态链接的本质,是把多个 .o 文件和静态库“糊”成一个独立的可执行文件。课上用 hello.c 和 code.c 做了一个极简但足够说明问题的实验。

    2.1 实验环境:两个 C 文件,三个编译阶段

    /* hello.c */
    int run(); /* 声明外部函数 */
    int main() {
    printf("hello");
    run();
    return 0;
    }

    /* code.c */
    int run() {
    printf("run func");
    return 0;
    }

    编译为 .o:

    gcc -c hello.c -o hello.o
    gcc -c code.c -o code.o

    在这里插入图片描述


    2.2 符号表:Defined vs Undefined

    用 readelf -s hello.o 查看符号表:

    • main、printf(实际底层是 _fputs 等)在 hello.o 中标记为 UND(Undefined)——我不知道它们在哪。在这里插入图片描述

    • run 同样标记为 UND,因为 hello.c 里没有它的定义。

    而在 code.o 中,run 被标记为 FUNC 且已定义。链接器的核心工作就是:拿着自己未定义的符号,去其他模块找定义。在这里插入图片描述


    2.3 地址重定位:call 后面为什么先是 0?

    用 objdump -d hello.o 反汇编,查看 main 函数里的调用:

    call 0 <?printf>
    call 0 <run>

    在这里插入图片描述

    老师强调:编译阶段根本不知道 printf 和 run 在内存的哪个位置,所以编译器只能把跳转地址暂时填成 0。这个 0 是一个占位符。

    当执行链接:

    gcc hello.o code.o -o main.exe

    在这里插入图片描述

    链接器完成两件事:

  • 合并 Section:把 hello.o 和 code.o 的 .text 合并成一个大 .text,.data 合并成一个大 .data。
  • 地址重定位:确定每个函数在最终可执行文件中的虚拟地址(比如 run 被安排在了 0x400520),然后把所有 call 0 修正为 call 0x400520。
  • 链接后再 objdump -d main.exe,你会看到 call 指令后面的地址已经被填上了真实的虚拟地址。这个过程就叫“链接时地址重定位”。

    也正因 .o 文件里的地址在链接时会被“重新修改”,.o 被称为可重定位目标文件(Relocatable Object File)。


    下面是静态链接中符号解析与地址重定位的完整流程:

    #mermaid-svg-GN2VoRr2yn1QsvV1{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GN2VoRr2yn1QsvV1 .error-icon{fill:#552222;}#mermaid-svg-GN2VoRr2yn1QsvV1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GN2VoRr2yn1QsvV1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .marker.cross{stroke:#333333;}#mermaid-svg-GN2VoRr2yn1QsvV1 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GN2VoRr2yn1QsvV1 p{margin:0;}#mermaid-svg-GN2VoRr2yn1QsvV1 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster-label text{fill:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster-label span{color:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster-label span p{background-color:transparent;}#mermaid-svg-GN2VoRr2yn1QsvV1 .label text,#mermaid-svg-GN2VoRr2yn1QsvV1 span{fill:#333;color:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .node rect,#mermaid-svg-GN2VoRr2yn1QsvV1 .node circle,#mermaid-svg-GN2VoRr2yn1QsvV1 .node ellipse,#mermaid-svg-GN2VoRr2yn1QsvV1 .node polygon,#mermaid-svg-GN2VoRr2yn1QsvV1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .rough-node .label text,#mermaid-svg-GN2VoRr2yn1QsvV1 .node .label text,#mermaid-svg-GN2VoRr2yn1QsvV1 .image-shape .label,#mermaid-svg-GN2VoRr2yn1QsvV1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-GN2VoRr2yn1QsvV1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .rough-node .label,#mermaid-svg-GN2VoRr2yn1QsvV1 .node .label,#mermaid-svg-GN2VoRr2yn1QsvV1 .image-shape .label,#mermaid-svg-GN2VoRr2yn1QsvV1 .icon-shape .label{text-align:center;}#mermaid-svg-GN2VoRr2yn1QsvV1 .node.clickable{cursor:pointer;}#mermaid-svg-GN2VoRr2yn1QsvV1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .arrowheadPath{fill:#333333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GN2VoRr2yn1QsvV1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GN2VoRr2yn1QsvV1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GN2VoRr2yn1QsvV1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster text{fill:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 .cluster span{color:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GN2VoRr2yn1QsvV1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GN2VoRr2yn1QsvV1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-GN2VoRr2yn1QsvV1 .icon-shape,#mermaid-svg-GN2VoRr2yn1QsvV1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GN2VoRr2yn1QsvV1 .icon-shape p,#mermaid-svg-GN2VoRr2yn1QsvV1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GN2VoRr2yn1QsvV1 .icon-shape .label rect,#mermaid-svg-GN2VoRr2yn1QsvV1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GN2VoRr2yn1QsvV1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GN2VoRr2yn1QsvV1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GN2VoRr2yn1QsvV1 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    可执行文件 main.exe

    链接阶段

    编译阶段

    源码

    hello.cmain() 调用 printf、run

    code.crun() 定义

    hello.omain: call 0 call 0

    code.orun: FUNC 已定义

    合并 Section.text / .data

    符号解析UND → 找到定义

    地址重定位call 0 → call 0x400520

    call 0x400520真实虚拟地址已填入

    图解: 编译阶段 call 后的地址是占位符 0;链接阶段完成 Section 合并、符号解析与地址重定位,最终把真实虚拟地址填入 call 指令。

    三、程序加载与虚拟地址空间:程序没有加载前,有地址吗?

    这是课堂上抛出的一个核心思考题,也是很多操作系统的“分水岭”知识点。

    3.1 有!磁盘上的逻辑地址,就是虚拟地址的“预览版”

    用 objdump -s 反汇编任何一个可执行程序(如 main.exe),最左侧会看到一连串地址:0x4003c8、0x4003d0、0x4003f0……在这里插入图片描述

    这些地址是什么?它们是程序在磁盘上就已经确定的编制。现代操作系统采用 平坦模式(Flat Model),即可执行程序内部从 0 开始统一编址。这个磁盘上的地址称为逻辑地址或线性地址。

    在 Linux 系统下,逻辑地址(磁盘视角)、虚拟地址(操作系统视角)、物理地址(真实内存视角)三位一体。只是在磁盘上我们习惯叫逻辑地址,加载到内存语境后(在PCB等那边),它们就是虚拟地址。程序还没加载时,内部函数调用的地址就已经编好了——否则链接器凭什么重写 call 指令?

    3.2 加载四步曲:从文件系统到 CPU 取指

    老师用一张大图把加载流程串了起来:

    第一步:文件系统定位
    操作系统通过路径解析找到 inode,进而找到磁盘上的数据块(Data Block)。可执行程序本质上也是文件,也要走文件系统这一套。

    第二步:创建进程骨架
    内核创建 task_struct(PCB)、mm_struct(内存描述符)、vm_area_struct 链表(虚拟内存区域)。此时进程有了“户口”,但地址空间还是空的。

    第三步:初始化地址空间与页表映射
    操作系统读取 ELF 的 Program Header Table,用每个 Segment 的虚拟起止地址(start ~ end)去初始化 vm_area_struct 的各个区域(代码区、数据区、BSS 区等)。随后把代码和数据加载到物理内存,建立页表映射——页表左半边是虚拟地址,右半边是物理地址。

    第四步:CPU 启动执行
    读取 ELF Header 中的 Entry Point(即 _start 的虚拟地址,如 0x400430),把它填入 CPU 的指令寄存器(EIP/RIP)。CPU 开始取指执行。

    3.3 权限管理与页表初始化

    操作系统并不“认识”哪个区域是代码、哪个是数据,它只读 ELF 的 Program Header。如果某个 Segment 标记为 R E(只读、可执行),操作系统初始化页表时就把这段映射为只读;如果标记为 RW,则赋予读写权限。程序的权限管理,在加载那一刻就由 ELF 格式决定了。

    3.4 一个极易忽略的细节:_start 才是入口

    很多同学以为程序入口是 main。但通过 objdump 和 readelf -h 都能看到,Entry Point 指向的是 _start。_start 由链接器(或 C 运行时库)提供,它的职责包括:

    • 设置栈帧;
    • 初始化 .data 段(把初始化的全局变量搬到位);
    • 清零 .bss 段;
    • 打开标准输入、标准输出、标准错误;
    • 调用动态链接器,加载依赖的动态库;
    • 最后调用 libc_start_main,进而调用用户写的 main。

    你的 main 函数执行完毕后,控制权回到 _start 的收尾逻辑,处理返回值并调用 exit 终止进程。main 只是“被调用者”。


    下面是程序加载四步曲的完整流程,从文件系统定位到 CPU 取指执行:

    #mermaid-svg-MDEZpS1jP6Bac7Pr{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MDEZpS1jP6Bac7Pr .error-icon{fill:#552222;}#mermaid-svg-MDEZpS1jP6Bac7Pr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MDEZpS1jP6Bac7Pr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .marker.cross{stroke:#333333;}#mermaid-svg-MDEZpS1jP6Bac7Pr svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MDEZpS1jP6Bac7Pr p{margin:0;}#mermaid-svg-MDEZpS1jP6Bac7Pr .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster-label text{fill:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster-label span{color:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster-label span p{background-color:transparent;}#mermaid-svg-MDEZpS1jP6Bac7Pr .label text,#mermaid-svg-MDEZpS1jP6Bac7Pr span{fill:#333;color:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .node rect,#mermaid-svg-MDEZpS1jP6Bac7Pr .node circle,#mermaid-svg-MDEZpS1jP6Bac7Pr .node ellipse,#mermaid-svg-MDEZpS1jP6Bac7Pr .node polygon,#mermaid-svg-MDEZpS1jP6Bac7Pr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .rough-node .label text,#mermaid-svg-MDEZpS1jP6Bac7Pr .node .label text,#mermaid-svg-MDEZpS1jP6Bac7Pr .image-shape .label,#mermaid-svg-MDEZpS1jP6Bac7Pr .icon-shape .label{text-anchor:middle;}#mermaid-svg-MDEZpS1jP6Bac7Pr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .rough-node .label,#mermaid-svg-MDEZpS1jP6Bac7Pr .node .label,#mermaid-svg-MDEZpS1jP6Bac7Pr .image-shape .label,#mermaid-svg-MDEZpS1jP6Bac7Pr .icon-shape .label{text-align:center;}#mermaid-svg-MDEZpS1jP6Bac7Pr .node.clickable{cursor:pointer;}#mermaid-svg-MDEZpS1jP6Bac7Pr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .arrowheadPath{fill:#333333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDEZpS1jP6Bac7Pr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MDEZpS1jP6Bac7Pr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDEZpS1jP6Bac7Pr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster text{fill:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr .cluster span{color:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MDEZpS1jP6Bac7Pr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MDEZpS1jP6Bac7Pr rect.text{fill:none;stroke-width:0;}#mermaid-svg-MDEZpS1jP6Bac7Pr .icon-shape,#mermaid-svg-MDEZpS1jP6Bac7Pr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MDEZpS1jP6Bac7Pr .icon-shape p,#mermaid-svg-MDEZpS1jP6Bac7Pr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MDEZpS1jP6Bac7Pr .icon-shape .label rect,#mermaid-svg-MDEZpS1jP6Bac7Pr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MDEZpS1jP6Bac7Pr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MDEZpS1jP6Bac7Pr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MDEZpS1jP6Bac7Pr :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    CPU

    ELF 解析

    内核

    文件系统

    路径解析 → inode找到磁盘数据块

    创建 task_struct(PCB)mm_struct、vm_area_struct

    初始化地址空间建立页表映射虚拟地址 ↔ 物理地址

    读取 Program Header Table按 Segment 权限映射

    读取 Entry Point_start 虚拟地址

    EIP/RIP 填入 _start 地址开始取指执行

    图解: 操作系统先通过文件系统定位 ELF,创建进程骨架,再按 Program Header 初始化地址空间与页表,最后把 _start 的虚拟地址填入 CPU 指令寄存器开始执行。

    四、动态链接与共享库:GOT/PLT 与延迟绑定

    静态链接虽然简单,但有个致命缺陷:每个可执行文件都独立拷贝一份库代码。如果系统里运行着 20 个 C 程序,内存里就有 20 份 printf 的代码,浪费磁盘和内存。动态链接(Dynamic Linking)解决了这个问题。

    4.1 动态库的宏观共享机制

    动态库(.so,Shared Object)是独立文件。当进程 A 加载 libc.so 后,它通过页表映射到自己的共享区。如果进程 B 也需要 libc.so,内核只需把同一份物理内存映射到 B 的虚拟地址空间。代码段是只读的,所以多进程可以安全共享同一份物理副本。这也是为什么动态库又叫共享库。

    4.2 动态链接器(ld-linux.so)

    任何一个动态链接的 ELF 程序,除了依赖 C 标准库,还会默认依赖一个库:/lib64/ld-linux-x86-64.so.2(或类似名称)。这就是动态链接器。在这里插入图片描述

    程序启动时,_start 会先调用它。动态链接器根据环境变量 LD_LIBRARY_PATH、/etc/ld.so.conf 配置文件以及系统默认路径,去搜索并加载程序依赖的所有 .so。只有当这些库都被找到、映射、重定位完毕后,才会把控制权交回给 main。

    4.3 位置无关代码(PIC)与 GOT

    动态库可以被加载到内存的任意位置,那么库内部函数互相调用时怎么办?如果写死绝对地址,库一旦被映射到不同基址就全乱套了。

    解决方案是 PIC(Position Independent Code,位置无关代码)。库内部的编址也采用“从 0 开始的偏移量”。当库被映射到进程虚拟空间的某个起始地址(比如 0x7f1234000),库内某个函数的入口是在库内偏移 0x233 处,那么实际虚拟地址就是库起始地址 + 偏移量。

    但这还不够。因为主程序调用库函数时,代码段是只读的,你不能直接在主程序的代码里写死库函数的最终地址。怎么办?

    GOT(Global Offset Table,全局偏移表)登场。

    • GOT 存放在数据段(可读写),每个进程有自己独立的 GOT。
    • 可执行程序不在代码段直接写 call printf,而是 call printf@GOT。
    • 链接时,GOT 表项先记录库的偏移量和库名;加载完成后,动态链接器把库的实际起始虚拟地址填进 GOT。
    • 从此,代码段一行不改,只改数据段的 GOT 表,程序就能正确跳到库函数。

    这完美解决了“代码段只读”与“需要动态修正地址”之间的矛盾。

    4.4 PLT:延迟绑定(Lazy Binding)

    如果一个程序依赖 10 个动态库、涉及 1000 个库函数,启动时就把这 1000 个地址全部解析并填入 GOT,会非常慢。很多函数可能整个生命周期都不会被调用。

    PLT(Procedure Linkage Table,过程链接表) 实现了延迟绑定:

    • 代码中 call printf 实际上先跳到 printf@PLT。
    • 第一次调用时,PLT 是一段“桩代码”,它会把控制权交给动态链接器:“帮我找一下 printf 的真身在哪”。
    • 动态链接器找到真实地址后,把地址写回 GOT 表。
    • 第二次再调用 printf,直接通过 GOT 跳转到真实地址,不再经过 PLT 解析。

    这就是“首次调用触发重定位,后续调用直查 GOT”的延迟绑定机制,兼顾了启动速度与运行效率。

    下面是首次调用 printf 时延迟绑定的完整流程图,标注了代码段(只读)与数据段(可写)的边界,以及 GOT 表项地址的变化过程:

    #mermaid-svg-DCXab3lINhPJaEcz{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DCXab3lINhPJaEcz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DCXab3lINhPJaEcz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DCXab3lINhPJaEcz .error-icon{fill:#552222;}#mermaid-svg-DCXab3lINhPJaEcz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DCXab3lINhPJaEcz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DCXab3lINhPJaEcz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DCXab3lINhPJaEcz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DCXab3lINhPJaEcz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DCXab3lINhPJaEcz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DCXab3lINhPJaEcz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DCXab3lINhPJaEcz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DCXab3lINhPJaEcz .marker.cross{stroke:#333333;}#mermaid-svg-DCXab3lINhPJaEcz svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DCXab3lINhPJaEcz p{margin:0;}#mermaid-svg-DCXab3lINhPJaEcz .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-DCXab3lINhPJaEcz .cluster-label text{fill:#333;}#mermaid-svg-DCXab3lINhPJaEcz .cluster-label span{color:#333;}#mermaid-svg-DCXab3lINhPJaEcz .cluster-label span p{background-color:transparent;}#mermaid-svg-DCXab3lINhPJaEcz .label text,#mermaid-svg-DCXab3lINhPJaEcz span{fill:#333;color:#333;}#mermaid-svg-DCXab3lINhPJaEcz .node rect,#mermaid-svg-DCXab3lINhPJaEcz .node circle,#mermaid-svg-DCXab3lINhPJaEcz .node ellipse,#mermaid-svg-DCXab3lINhPJaEcz .node polygon,#mermaid-svg-DCXab3lINhPJaEcz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DCXab3lINhPJaEcz .rough-node .label text,#mermaid-svg-DCXab3lINhPJaEcz .node .label text,#mermaid-svg-DCXab3lINhPJaEcz .image-shape .label,#mermaid-svg-DCXab3lINhPJaEcz .icon-shape .label{text-anchor:middle;}#mermaid-svg-DCXab3lINhPJaEcz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DCXab3lINhPJaEcz .rough-node .label,#mermaid-svg-DCXab3lINhPJaEcz .node .label,#mermaid-svg-DCXab3lINhPJaEcz .image-shape .label,#mermaid-svg-DCXab3lINhPJaEcz .icon-shape .label{text-align:center;}#mermaid-svg-DCXab3lINhPJaEcz .node.clickable{cursor:pointer;}#mermaid-svg-DCXab3lINhPJaEcz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DCXab3lINhPJaEcz .arrowheadPath{fill:#333333;}#mermaid-svg-DCXab3lINhPJaEcz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DCXab3lINhPJaEcz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DCXab3lINhPJaEcz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DCXab3lINhPJaEcz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DCXab3lINhPJaEcz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DCXab3lINhPJaEcz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DCXab3lINhPJaEcz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DCXab3lINhPJaEcz .cluster text{fill:#333;}#mermaid-svg-DCXab3lINhPJaEcz .cluster span{color:#333;}#mermaid-svg-DCXab3lINhPJaEcz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DCXab3lINhPJaEcz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DCXab3lINhPJaEcz rect.text{fill:none;stroke-width:0;}#mermaid-svg-DCXab3lINhPJaEcz .icon-shape,#mermaid-svg-DCXab3lINhPJaEcz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DCXab3lINhPJaEcz .icon-shape p,#mermaid-svg-DCXab3lINhPJaEcz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DCXab3lINhPJaEcz .icon-shape .label rect,#mermaid-svg-DCXab3lINhPJaEcz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DCXab3lINhPJaEcz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DCXab3lINhPJaEcz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DCXab3lINhPJaEcz :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    动态链接器ld-linux.so

    数据段(可写,RW)

    代码段(只读,R E)

    第一次:GOT 未解析

    回填 GOT 表项

    初始状态

    第二次调用直接跳转

    call printf@PLT

    PLT 桩代码jmp *GOT[printf]

    push 重定位索引(如 0x2)

    jmp 动态链接器入口PLT[0]

    GOT[printf] = PLT+6(初始指向下一条指令)

    GOT[printf] = 0x7f…(printf 真实地址)

    查找 printf 符号定位 libc.so 中真实地址

    把真实地址写回 GOT[printf]

    流程解读:

  • 第一次调用:call printf@PLT 进入 PLT 桩代码,此时 GOT[printf] 还指向 PLT 内部的下一条指令(PLT+6),于是继续 push 重定位索引并跳转到动态链接器。
  • 动态链接器解析:根据重定位索引查找 printf 在 libc.so 中的真实虚拟地址。
  • 回填 GOT:动态链接器把真实地址写回数据段的 GOT[printf],代码段一行未改。
  • 第二次调用:call printf@PLT 再次进入桩代码,此时 GOT[printf] 已指向真实地址,直接 jmp 到 printf 本体,不再经过动态链接器。
  • 4.5 库依赖库:每个 ELF 都有自己的 GOT

    不仅是可执行程序调用动态库,动态库之间也可以相互依赖。比如库 A 调用库 B 的函数。因为每个 ELF(无论可执行文件还是 .so)都有自己的代码段和数据段,所以每个库都有自己的 GOT。库 A 通过查自己的 GOT,找到库 B 的方法地址。这也是 ELF 格式统一性的体现。


    五、总结:一张图看清从源码到 CPU 的全链路

    这堂课的终极意义,是把看似孤立的编译原理、操作系统文件系统、内存管理、进程加载串成了一条线:

  • 编译与静态链接:多个 .c → .o → 合并 Section、解析符号、地址重定位 → 生成 ELF 可执行文件。此时程序内部已基于平坦模式从 0 编址(逻辑地址/虚拟地址)。
  • 加载准备:操作系统通过文件系统找到 ELF,读取 ELF Header 拿到 Entry Point,读取 Program Header 初始化 mm_struct 和 vm_area_struct。
  • 内存映射:Segment 被加载到物理内存,页表建立虚拟地址到物理地址的映射。权限由 ELF 的 Flag 位决定。
  • 启动与动态链接:CPU 的 EIP 被填入 _start 虚拟地址。_start 初始化运行环境,调用动态链接器加载 .so,通过 GOT/PLT 完成运行时重定位。
  • 进入 main:一切就绪后,最终调用 libc_start_main,进入用户编写的 main 函数。
  • 虚拟地址空间是这一切的核心桥梁:它让编译器、链接器、操作系统、CPU 能用同一套“地址语言”对话。编译器在磁盘上按虚拟地址编好程序,操作系统用这些虚拟地址初始化进程空间,CPU 通过页表把虚拟地址翻译为物理地址取指执行。

    下堂课,我们将进入进程间通信(IPC)的世界,开发环境也将切换为 C++ 与 VS Code。但那已经是另一个故事了。


    附:课堂核心工具速查表

    工具/命令作用
    readelf -S <file> 查看 Section Header Table(编译链接视角)
    readelf -l <file> 查看 Program Header Table 及 Section → Segment 映射(执行加载视角)
    readelf -h <file> 查看 ELF Header(Magic、Entry Point、文件类型等)
    readelf -s <file> 查看符号表(Defined / Undefined 符号)
    objdump -d <file> 反汇编代码段,查看 call 指令地址,验证重定位
    ldd <executable> 查看可执行文件依赖的动态库
    gcc -c 编译不链接,生成 .o 可重定位目标文件
    ./a.out 背后的 _start 程序真正入口,负责初始化与动态链接调度
    file <file> 命令用于识别文件类型在这里插入图片描述

    下面是整篇文章的完整链路总览图,从源码到 CPU 取指执行:

    #mermaid-svg-rTKN0xWyUauOkSWv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rTKN0xWyUauOkSWv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rTKN0xWyUauOkSWv .error-icon{fill:#552222;}#mermaid-svg-rTKN0xWyUauOkSWv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rTKN0xWyUauOkSWv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rTKN0xWyUauOkSWv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rTKN0xWyUauOkSWv .marker.cross{stroke:#333333;}#mermaid-svg-rTKN0xWyUauOkSWv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rTKN0xWyUauOkSWv p{margin:0;}#mermaid-svg-rTKN0xWyUauOkSWv .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-rTKN0xWyUauOkSWv .cluster-label text{fill:#333;}#mermaid-svg-rTKN0xWyUauOkSWv .cluster-label span{color:#333;}#mermaid-svg-rTKN0xWyUauOkSWv .cluster-label span p{background-color:transparent;}#mermaid-svg-rTKN0xWyUauOkSWv .label text,#mermaid-svg-rTKN0xWyUauOkSWv span{fill:#333;color:#333;}#mermaid-svg-rTKN0xWyUauOkSWv .node rect,#mermaid-svg-rTKN0xWyUauOkSWv .node circle,#mermaid-svg-rTKN0xWyUauOkSWv .node ellipse,#mermaid-svg-rTKN0xWyUauOkSWv .node polygon,#mermaid-svg-rTKN0xWyUauOkSWv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rTKN0xWyUauOkSWv .rough-node .label text,#mermaid-svg-rTKN0xWyUauOkSWv .node .label text,#mermaid-svg-rTKN0xWyUauOkSWv .image-shape .label,#mermaid-svg-rTKN0xWyUauOkSWv .icon-shape .label{text-anchor:middle;}#mermaid-svg-rTKN0xWyUauOkSWv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rTKN0xWyUauOkSWv .rough-node .label,#mermaid-svg-rTKN0xWyUauOkSWv .node .label,#mermaid-svg-rTKN0xWyUauOkSWv .image-shape .label,#mermaid-svg-rTKN0xWyUauOkSWv .icon-shape .label{text-align:center;}#mermaid-svg-rTKN0xWyUauOkSWv .node.clickable{cursor:pointer;}#mermaid-svg-rTKN0xWyUauOkSWv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rTKN0xWyUauOkSWv .arrowheadPath{fill:#333333;}#mermaid-svg-rTKN0xWyUauOkSWv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rTKN0xWyUauOkSWv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rTKN0xWyUauOkSWv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rTKN0xWyUauOkSWv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rTKN0xWyUauOkSWv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rTKN0xWyUauOkSWv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rTKN0xWyUauOkSWv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rTKN0xWyUauOkSWv .cluster text{fill:#333;}#mermaid-svg-rTKN0xWyUauOkSWv .cluster span{color:#333;}#mermaid-svg-rTKN0xWyUauOkSWv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rTKN0xWyUauOkSWv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rTKN0xWyUauOkSWv rect.text{fill:none;stroke-width:0;}#mermaid-svg-rTKN0xWyUauOkSWv .icon-shape,#mermaid-svg-rTKN0xWyUauOkSWv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rTKN0xWyUauOkSWv .icon-shape p,#mermaid-svg-rTKN0xWyUauOkSWv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rTKN0xWyUauOkSWv .icon-shape .label rect,#mermaid-svg-rTKN0xWyUauOkSWv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rTKN0xWyUauOkSWv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rTKN0xWyUauOkSWv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rTKN0xWyUauOkSWv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    进入 main

    启动与动态链接

    内存映射

    加载准备

    编译与静态链接

    多个 .c 文件

    .o 可重定位目标文件

    ELF 可执行文件(逻辑地址已编好)

    文件系统定位 ELF

    读取 ELF Header拿到 Entry Point

    读取 Program Header初始化 mm_struct

    Segment 加载到物理内存

    页表建立虚拟地址 ↔ 物理地址

    _start 入口

    动态链接器加载 .so

    GOT/PLT 运行时重定位

    libc_start_main → main

    图解: 从源码编译、静态链接生成 ELF,到操作系统加载、内存映射、动态链接,最终进入 main 函数——虚拟地址空间贯穿始终,是这一切的核心桥梁。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux-System】【ELF加载揭秘:从磁盘到CPU的硬核旅程】流食般投喂
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!