欢迎光临
我们一直在努力

CSAPP=程序如何被「翻译」成可执行文件

<?php
/**
* 概念 2~6:程序如何被「翻译」成可执行文件
* ———————————————————————-
* 大白话:
* 你写的 hello.c 是给「人」看的文本。CPU 看不懂文本,只认 0/1 机器码。
* 从源代码到能运行的程序,要走 4 个阶段,像工厂流水线:
*
* hello.c –[预处理 cpp]–> hello.i
* hello.i –[编译 cc1 ]–> hello.s (汇编代码)
* hello.s –[汇编 as ]–> hello.o (机器码,二进制目标文件)
* hello.o –[链接 ld ]–> hello (最终可执行文件)
*
* 下面用 PHP「模拟」这条流水线:把每一步的输入输出打印出来,
* 让你直观看到每个阶段干了啥。我们不真的调 gcc,而是手写一个迷你版。
*/

// — 概念2:程序被翻译成不同格式(这就是整条流水线的总览)—

// — 概念3:预处理阶段 —
// 大白话:处理所有 # 开头的指令。比如 #include 就是「把头文件内容整段贴进来」,
// #define 就是「文本替换」。结果还是 C 代码,只是被展开了。
function stage_preprocess(string $source): string
{
echo step("3. 预处理 (cpp)", "处理 #include / #define,做文本展开和替换");

// 模拟 #include <stdio.h>:贴入 printf 的声明
$included = "/* 来自 stdio.h 的声明(被 #include 贴进来了) */\\n"
. "int printf(const char *format, …);\\n\\n";
$source = preg_replace('/#include\\s*<[^>]+>\\s*/', $included, $source);

// 模拟 #define MSG "…" :先抓出定义,再全文替换
if (preg_match('/#define\\s+(\\w+)\\s+(.+)/', $source, $m)) {
$name = $m[1];
$value = trim($m[2]);
$source = preg_replace('/#define\\s+\\w+\\s+.+\\n/', '', $source); // 删掉 #define 这行
$source = str_replace($name, $value, $source); // 把名字替换成值
echo " 展开宏: $name → $value\\n";
}

return $source;
}

// — 概念4:编译阶段 —
// 大白话:把 C 代码翻译成「汇编代码」(.s)。汇编是给人看的、最接近机器的语言,
// 每条几乎对应一条 CPU 指令。这里我们生成一段假装的汇编文本。
function stage_compile(string $preprocessed): string
{
echo step("4. 编译 (cc1)", "C 代码 → 汇编代码 (.s),离机器更近一步");

// 从代码里把要打印的字符串抠出来,塞进汇编模板
preg_match('/printf\\(\\s*"([^"]*)"/', $preprocessed, $m);
$msg = $m[1] ?? "hello, world";

return implode("\\n", [
' .section .rodata',
'.LC0:',
' .string "' . $msg . '" ; 要打印的字符串放在只读数据区',
' .text',
' .globl main',
'main:',
' leaq .LC0(%rip), %rdi ; 把字符串地址放进参数寄存器',
' call printf ; 调用 printf(现在还不知道它在哪)',
' movl $0, %eax ; 返回值 0',
' ret',
]);
}

// — 概念5:汇编阶段 —
// 大白话:把汇编代码翻译成真正的「机器码」(0/1),打包成「可重定位目标文件」.o。
// .o 已经是二进制了,人直接看是天书。它里面有「还没填好的空」——
// 比如 printf 到底在哪,要等链接阶段才知道。
function stage_assemble(string $asm): array
{
echo step("5. 汇编 (as)", "汇编代码 → 机器码 (.o),二进制目标文件");

// 模拟:把每行汇编「编码」成假的机器码字节
$machineCode = [];
foreach (explode("\\n", $asm) as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '.') || str_ends_with($line, ':')) {
continue;
}
// 拿这行算个 hash 当作「机器码字节」,纯属示意
$machineCode[] = strtoupper(substr(md5($line), 0, 4));
}

return [
'code' => $machineCode,
// 这就是「还没填好的空」:本文件用到了 printf,但不知道地址
'unresolved' => ['printf'],
];
}

// — 概念6:链接阶段 —
// 大白话:你的程序用到了 printf,但 printf 的代码在系统的标准库 libc 里。
// 链接器(ld)负责把你的 .o 和 libc 里的 printf「拼」到一起,
// 把之前那些「还没填好的空」填上正确地址,最后产出能直接运行的可执行文件。
function stage_link(array $objectFile): string
{
echo step("6. 链接 (ld)", "把你的 .o 和库(libc 里的 printf)拼成最终可执行文件");

foreach ($objectFile['unresolved'] as $symbol) {
echo " 解析符号: '$symbol' → 在 libc.so 中找到,填入真实地址 0x401050\\n";
}

return "hello (可执行文件)";
}

// 把 2~6 串成完整流水线
function demo_compile_pipeline(): void
{
echo section("概念2~6:从源代码到可执行文件的完整流水线");

$helloC = <<<C
#include <stdio.h>
#define MSG "hello, world\\\\n"
int main() {
printf(MSG);
return 0;
}
C;

echo "原始源代码 hello.c(给人看的文本):\\n";
echo indent($helloC) . "\\n\\n";

$i = stage_preprocess($helloC);
echo indent($i) . "\\n";

$s = stage_compile($i);
echo indent($s) . "\\n";

$o = stage_assemble($s);
echo " 生成机器码字节: " . implode(' ', $o['code']) . "\\n";
echo " 尚未解析的符号: " . implode(', ', $o['unresolved']) . "\\n\\n";

$exe = stage_link($o);
echo "\\n最终产物: $exe —— 现在它就能被加载到内存里跑起来了。\\n";
}

赞(0)
未经允许不得转载:171主机测评 » CSAPP=程序如何被「翻译」成可执行文件
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址