惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
Help Net Security
Help Net Security
P
Privacy International News Feed
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
AWS News Blog
AWS News Blog
K
Kaspersky official blog
A
Arctic Wolf
Latest news
Latest news
T
Threat Research - Cisco Blogs
L
LINUX DO - 最新话题
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
月光博客
月光博客
N
News and Events Feed by Topic
Jina AI
Jina AI
博客园 - 司徒正美
WordPress大学
WordPress大学
罗磊的独立博客
雷峰网
雷峰网
AI
AI
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Security @ Cisco Blogs
博客园 - 三生石上(FineUI控件)
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cisco Blogs
博客园 - 【当耐特】
The Hacker News
The Hacker News
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
博客园 - Franky
S
SegmentFault 最新的问题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Cloudbric
Cloudbric
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
Last Week in AI
Last Week in AI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News

博客园 - ffl

[AI翻译]我如何用大模型写软件 [AI翻译]来自Zig的教训 [AI翻译] 标题:Agentic AI 手册:生产级模式 [AI翻译]:我们如何让 Python 的 packaging 库提速三倍 翻译:Akin 的航天器设计法则 Navida和Groq的交易资金多么? 翻译:这并不是未来 一个完整的软件开发过程,AI在哪些地方加速? 从业务读写流程出发,理解各种分布式系统组件的意义 类型标注,太多和太少一样糟糕 【AI翻译】什么是第三方API?优势、应用场景与最佳实践 【AI翻译】分布式系统中的心跳机制 【AI翻译】Python 3.14来了,有多快? Argo CD 与 Kubernetes 资源关系简述 技术文章阅读todo-list DLM(Diffusion Language Model) vs AR(Autoregressive) 标注的原理:少而完备,监督模型训练的根本 几何平均比算术平均对极值不敏感。 对比理解:什么是AI味浓厚的文章? 重要文章:Asymmetry of verification and verifier’s law
用AI像大一新生一样学东西
ffl · 2026-06-18 · via 博客园 - ffl

提示词:【解释 https://devblogs.microsoft.com/oldnewthing/20031008-00/?p=42223 ,我还是没看懂,因为低地址,高地址,fixup等等,实际上我以前理解过,但是又忘记了,你得把我当成大一新生一样,从基础开始,一步步解释,示意,每一个可能的「常识」都要教学。】

AI step by step 教学:

我会分成 6 级台阶,每一级都用 ASCII 图,不跳步。


0. 整体路线图(你先有个心理预期)

我们要爬的楼梯是这样的:

  1. 什么是“内存地址”?高地址 / 低地址是啥?
  2. 什么是 16 位 / 32 位数?为什么要“拆成高 16 + 低 16”?
  3. CPU 怎么把一个 32 位地址装进寄存器?(RISC 的限制)
  4. 什么是 DLL、什么是 relocation / fixup?为什么要“改地址”?
  5. 如果随便移动 DLL,会发生什么灾难?(这一步是关键)
  6. 所以:为什么 Windows 只能选 64KB 作为分配粒度?

你随时可以说“这一级再慢一点”,我们就停在某一级多举几个例子。


1. 什么是“内存地址”?高地址 / 低地址是啥?

先忘掉 CPU,忘掉 Windows,就想象一条很长的格子带:

内存(抽象示意):

  +--------+--------+--------+--------+--------+--------+
  |  单元  |  单元  |  单元  |  单元  |  单元  |  单元  |  ...
  +--------+--------+--------+--------+--------+--------+
    0x0000   0x0001   0x0002   0x0003   0x0004   0x0005
  • 每一个格子就是一个“内存单元”(比如 1 字节)。
  • 每个格子有一个编号,这个编号就是地址
  • 地址是一个整数,用十六进制写起来比较方便,比如 0x00000x0001

低地址 / 高地址:

  低地址  →  →  →  →  →  →  →  →  →  →  高地址
  0x0000   0x0001   0x0002   ...        0xFFFF
  • 数字小的叫“低地址”(low address)
  • 数字大的叫“高地址”(high address)

就像楼层:1 楼比 20 楼“低”。


2. 什么是 16 位 / 32 位数?为什么会有“高 16 位 / 低 16 位”?

先看一个 32 位的数,比如:

0x1234ABCD

用二进制表示是 32 个 bit(位):

0x1234ABCD = 0001 0010 0011 0100 1010 1011 1100 1101
              ↑               ↑
            高 16 位         低 16 位

我们可以把它拆成两半:

高 16 位:0x1234
低 16 位:0xABCD

ASCII 图:

0x1234ABCD
  └─┬──┘└─┬──┘
   高16   低16

为什么要拆?

因为有些 CPU 的指令集里:

  • 一条指令装不下完整的 32 位立即数(immediate)
  • 只能一次装 16 位
  • 所以要用两条指令:先装高 16,再装低 16

这就引出了下一步。


3. CPU 怎么把一个 32 位地址装进寄存器?(RISC 的限制)

在很多 RISC 架构(Alpha / MIPS / PowerPC / ARM)里,没有这种指令:

MOV R1, 0x1234ABCD   ; 一次性把 32 位常量装进寄存器

它们只能这样干(伪代码):

LDAH R1, 0x1234      ; Load high 16 bits
LDA  R1, 0xABCD(R1)  ; 在 R1 的基础上加上低 16 位

ASCII 图:

步骤 1:LDAH R1, 0x1234

  R1 = 0x1234 0000

步骤 2:LDA R1, 0xABCD(R1)

  R1 = 0x1234 0000 + 0xABCD
     = 0x1234 ABCD

所以你可以记住一个简单事实:

在这些 CPU 上,“加载一个 32 位地址” = “高 16 位指令 + 低 16 位指令”。

这就是后面“fixup 要修两处”的根源。


4. 什么是 DLL?什么是 relocation / fixup?

4.1 DLL 是什么?

简单讲:

  • EXE / DLL 里有代码和数据
  • 编译时,编译器不知道它们最终会被放到内存的哪个地址
  • 所以会先假设一个“理想地址”,比如:
    • 这个 DLL 以后会被放在 0x10000000 开头

比如 DLL 里有一段代码要访问一个全局变量 g_value

extern int g_value;

int foo() {
    return g_value + 1;
}

编译器会把 g_value 当成“在某个固定地址”,比如:

g_value 假设在:0x10008000

于是生成的机器码里,会出现“访问 0x10008000”这样的地址。

4.2 问题来了:实际加载时地址可能变了

当系统真正把 DLL 加载进内存时:

  • 可能 0x10000000 这块已经被别的 DLL 占了
  • 于是系统只好把这个 DLL 放到别的地方,比如 0x20000000

这时,原来写死在代码里的地址 0x10008000不对了,应该变成:

0x20008000

这就叫 relocation(重定位)
而对每一个需要修改的地方做的“改地址动作”,就叫 fixup

ASCII 图:

编译时假设:
  DLL 基址:0x10000000
  g_value: 0x10008000

实际加载:
  DLL 被放到:0x20000000
  g_value 实际:0x20008000

需要做的事:
  把代码里所有用到 0x10008000 的地方
  改成 0x20008000   ← 这一步就是 fixup

5. 如果 DLL 随便移动,会发生什么灾难?

现在把前面两件事放在一起:

  1. 地址是 32 位,要拆成高 16 + 低 16 来加载
  2. DLL 可能被移动,导致地址要改(fixup)

5.1 理想情况:只改低 16 位就够了

假设原地址是:

原地址:0x12340000
新地址:0x12348000   (只加了 0x8000 = 32KB)

拆开:

原:高 16 = 0x1234,低 16 = 0x0000
新:高 16 = 0x1234,低 16 = 0x8000

ASCII 图:

0x1234 0000  →  0x1234 8000
   高不变         高不变
   低变了         低变了

这种情况很好:

  • 高 16 位不变 → 不用改高位指令
  • 只改低 16 位 → fixup 简单、便宜

5.2 糟糕情况:跨过 64KB 边界,高 16 位也变了

看 Raymond Chen 文里的例子:

原地址:0x1234F000
新地址:0x12350000   (只加了 0x1000 = 4KB)

拆开:

原:高 16 = 0x1234,低 16 = 0xF000
新:高 16 = 0x1235,低 16 = 0x0000

ASCII 图:

0x1234 F000  →  0x1235 0000
   高:1234        高:1235   ← 变了!
   低:F000        低:0000

虽然只移动了 4KB,但因为跨过了一个 64KB 边界0x1234FFFF → 0x12350000),
高 16 位也变了

这会导致:

  • 低 16 位要改
  • 高 16 位也要改
  • 而且两者之间还有进位 / 借位的问题
  • 编译器可能把“加载高 16 位”的指令放在很远的地方,fixup 很难找到它

这就是:如果 DLL 可以以 4KB 粒度随便移动,fixup 会变得又多又复杂。

5.3 Alpha 的 signed 低 16 位,让事情更糟

Alpha 的低 16 位是有符号数,所以它经常这样写:

LDAH t1, 0x1235
LDA  t1, -0x5433(t1)

用来表示 0x1234ABCD

ASCII 图(你可以慢慢看几遍):

目标地址:0x1234ABCD

高 16 位:0x1235
低 16 位:-0x5433   (因为 0x10000 - 0xABCD = 0x5433)

组合过程:
  0x1235 0000
-        5433
-----------
  0x1234 ABCD

如果 DLL 移动后,地址从“64KB 的上半区”变到“下半区”,
signed 低 16 位的写法会完全变掉,
这就不是“简单加个偏移”能搞定的了。

所以:

只要 DLL 的移动不是 64KB 的整数倍,fixup 就会非常麻烦。


6. 所以:为什么 Windows 选 64KB 作为分配粒度?

现在我们把所有东西串起来:

  1. RISC CPU(Alpha/MIPS/PowerPC 等)
    • 加载 32 位地址 = 高 16 位指令 + 低 16 位指令
  2. DLL 可能因为地址冲突被移动
    • 需要对代码里的地址做 fixup
  3. 如果移动量不是 64KB 的整数倍
    • 可能跨 64KB 边界 → 高 16 位也要改
    • Alpha 还要处理 signed 低 16 位 → 更复杂
    • 编译器把高 16 位加载指令放得很远 → 很难找到一起改
  4. 如果强行支持“任意 4KB 粒度移动 DLL”
    • fixup 数量暴增
    • fixup 逻辑极其复杂
    • 性能和可靠性都很差

于是 Windows 做了一个非常务实的决定

地址空间分配粒度 = 64KB
也就是说:

  • DLL 只会被移动到 64KB 对齐的位置
  • 移动量永远是 64KB 的整数倍
  • 这样就可以保证:
    • 很多情况下只需要改低 16 位
    • 即使要改高 16 位,模式也比较简单
    • 编译器的优化(共享高 16 位加载)仍然成立

再用一个 ASCII 总结图收个尾:

地址空间(简化):

  ... ┆ 0x12330000
      ┆ 0x12340000   ← 64KB 对齐
      ┆ 0x12350000   ← 64KB 对齐
      ┆ 0x12360000
  ... ┆

规则:
  DLL 只能从一个 64KB 边界
  移动到另一个 64KB 边界

  ✔ 0x12340000 → 0x12350000   (OK,移动 64KB)
  ✘ 0x1234F000 → 0x12350000   (NO,只移动 4KB,会搞乱高 16 位)