尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
操作码揭秘:CPU如何读懂你的代码
一、先搞清楚一件事CPU 只认数字你写的代码不管是 C 还是 C#CPU 都看不懂。CPU 能读的只有内存里的一串数字。所以编译器要做的事就是把你的代码翻译成一串数字。你写的: a b c 变成数字: 3, 1, 2, 5问题来了CPU 看到3, 1, 2, 5这几个数怎么知道这是做加法而不是读文件答案是事先约好。有一张表规定好1 号 做加法 2 号 做减法 3 号 从内存读一个数 4 号 往内存写一个数 ...CPU 看到第一个数字是3查表知道要读内存于是就去读内存。这个1 号“2 号”3 号的编号就叫操作码opcode。就这么简单。它是一个编号一个 enum 值。二、一条指令的两半光有编号不够。你说做加法加谁和谁所以一条完整的指令要有两部分加法 rax rbx ↑ └───┬───┘ 操作码 操作数 做什么 对谁做操作码说要干什么。操作数说对谁干。写成字节的样子数字是我瞎编的意思对就行内存里: [ 01 ] [ 03 ] [ 04 ] ↑ ↑ ↑ 操作码 操作数1 操作数2 加法 3号 4号 寄存器 寄存器 含义: 把 3 号寄存器和 4 号寄存器加起来CPU 的工作循环就是从内存读一个字节查表看是几号操作再读几个字节知道对谁操作执行回到第 1 步读下一条一直这么转转到程序结束。三、动手造一台小机器光说还是抽象。咱们造一台最简单的虚拟机一共四条指令。先定操作码表0 HALT 停机 1 PUSH 把一个数放到栈上 2 ADD 把栈顶两个数加起来 3 PRINT 打印栈顶的数现在用它算 2 3程序 [ 1, 2, 1, 3, 2, 3, 0 ] ↑ ↑ ↑ ↑ ↑ ↑ ↑ PUSH 2 PUSH 3 ADD PRINT HALT一步步跑读到 1 (PUSH)再读到 2 → 栈: [2] 读到 1 (PUSH)再读到 3 → 栈: [2, 3] 读到 2 (ADD) → 栈: [5] 读到 3 (PRINT) → 屏幕打印 5 读到 0 (HALT) → 结束代码写出来大概三十行intprogram[]{1,2,1,3,2,3,0};intstack[64];intsp0;// 栈指针intpc0;// 当前读到程序的哪个位置for(;;){intopprogram[pc];// 读一个操作码switch(op){case1:// PUSHstack[sp]program[pc];// 再读一个数压栈break;case2:// ADDsp--;stack[sp-1]stack[sp-1]stack[sp];break;case3:// PRINTprintf(%d\n,stack[sp-1]);break;case0:// HALTreturn0;}}这就是一台完整的虚拟机。Lua、Python、Java 的核心跑的就是这个循环只是指令多几百条细节多很多。注意那个switch——操作码的用途就是用来 switch 的。它存在的全部意义就是让程序知道该跳到哪个分支。四、真实的 CPU 也是这样把上面那台小机器换成真 CPU原理一模一样只是编号是 Intel 和 ARM 定的不是你定的。举个最简单的例子。x86 里0x90 什么都不做NOP就一个字节0x90CPU 看到它就空转一拍。再看一条常用的0x55 把 rbp 寄存器的值压栈也是一个字节。为什么这么短因为**压栈和rbp被合在一个编号里了**——Intel 给八个寄存器各分了一个编号0x50到0x57分别对应 push 八个不同的寄存器。常用操作给短编号这是为了省内存。五、唯一需要知道的复杂之处这里只有一件事值得讲就是指令有多长。x86每条指令长度不一样0x90 1 个字节 0x55 1 个字节 0x48 0x89 0xe5 3 个字节 ... 最长能到 15 个字节好处是省空间常用指令只占一个字节。坏处是你不能从中间开始读。假设内存里是这样地址: 100 101 102 103 104 内容: 48 89 e5 90 55从 100 开始读CPU 知道48 89 e5是一条3 字节然后90是一条55是一条。读对了。但如果你从 101 开始读89 e5 90会被当成另一条完全不同的指令。整个后面全错。从 100 开始: [48 89 e5] [90] [55] ← 对 从 101 开始: [89 e5 90] [55] ... ← 全错所以 x86 的字节流必须从正确的起点开始解析。这就像一句没有空格的英文NOWHERE从头读是 “nowhere”从第三个字母读是 “here”。字节流也一样起点错了意思全变。ARM每条指令都是 4 字节手机 CPUARM和 RISC-V 换了个思路所有指令固定 4 字节。地址: 100-103 104-107 108-111 内容: 一条 一条 一条好处是永远不会读错位置随便从哪个 4 字节边界开始都对。而且 CPU 可以一次并行解码八条因为它提前就知道每条在哪。坏处是浪费空间。哪怕什么都不做这种最简单的操作也得占满 4 个字节。两边各有道理x86ARM指令长度1~15 字节固定 4 字节省空间省不省解码麻烦必须顺着读简单随便从哪读电脑 CPU 选了省空间手机 CPU 选了好解码。没有对错。六、这知识什么时候用得上你平时不会手写操作码。但有几个场景会撞上它。调试器下断点是在偷偷改你的代码x86 有个一字节指令0xCC 暂停交给调试器你在 IDE 里点一下行号设断点调试器实际做的事是原来: 48 89 e5 改成: CC 89 e5 ← 把第一个字节换成 CC原来的 48 存起来程序跑到这里就停住调试器接管。你按继续的时候它把48填回去再让程序接着跑。为什么选一字节的指令因为不管原来那条指令多长覆盖一个字节总是安全的。这也解释了一个现象断点如果下错位置落在指令中间反汇编窗口会显示一堆乱码。原因就是上面说的——起点错了后面全错。崩溃日志里的 “illegal instruction”看到SIGILL或者EXCEPTION_ILLEGAL_INSTRUCTION意思是CPU 读到了一个编号查表发现没这个号。什么情况下会这样基本都是程序跑到了不该去的地方函数指针被改坏了虚表被覆盖了栈溢出改掉了返回地址程序跳到了一片不是代码的内存上把数据当指令读自然查不到对应的编号。所以看到这个错误先怀疑有东西把内存写坏了不是某行逻辑算错了。游戏里给引擎函数打补丁想在某个引擎函数前后插点自己的代码埋点、改行为、热更新常见做法是改掉函数开头原来: 55 48 89 e5 ... 函数正常开头 改成: E9 xx xx xx xx 跳到我的代码去0xE9是跳转加 4 个字节的地址一共 5 字节。麻烦在哪5 个字节可能不是整数条指令。要覆盖 5 字节: 55 | 48 89 e5 | 8b 45 ... ↑ ↑ ↑ 1 3 第 5 个字节落在这条指令中间你把它切一半剩下那半截就是垃圾。所以得先算清楚指令边界把被切到的整条指令搬到别处去。MinHook、Detours 这些库内部都带一个小型的指令长度计算器就是为了干这件事。ARM 上这个问题不存在——4 字节一条边界天然对齐。七、如果你要自己写一个游戏里经常需要跑点脚本技能配置、对话逻辑、行为树。这些底下都是一套自定义操作码。几条建议定长指令。比如统一 32 位一条操作码放在固定的几位上。解码就是位移加掩码几乎不花时间而且工具好写。Lua 的做法可以直接抄┌────────┬────────┬────────┬────────┐ │ B │ C │ A │ 操作码 │ │ 9 位 │ 9 位 │ 8 位 │ 6 位 │ └────────┴────────┴────────┴────────┘ 32 位刚好一次读取操作码 6 位能放 64 条指令对大多数脚本语言够用。先把 switch 写通别一开始就优化。上面那三十行的switch版本足够跑起来。等 profiler 告诉你分派是瓶颈了再去换成跳转表computed goto那套。一定要同时写个反汇编器。就是把字节码打印成人能读的文本PUSH 2 PUSH 3 ADD PRINT HALT几十行代码的事但字节码出 bug 的时候没有它你会完全瞎掉。这是投入产出比最高的一个工具。总结操作码就是一个编号告诉机器该执行哪个操作。一条指令 操作码做什么 操作数对谁做。机器的工作就是个循环读一个编号 → 查表 → 执行 → 读下一个。你那三十行switch就是这个循环。真实 CPU 唯一多出来的复杂度是指令长度x86 变长省空间但必须顺着读ARM 定长费空间但随便从哪读都对。这个区别会一路影响到调试器怎么下断点、hook 库怎么改函数、崩溃日志为什么显示乱码。说白了就是操作码是 enum解释器是 switch。剩下的都是细节。
RELATED

相关推荐

eFuse+MCU:工业电源主动保护方案设计与实践

eFuse+MCU:工业电源主动保护方案设计与实践

项目的标题是一个原理图级的设计描述,但真正读懂它的人会发现,这背后是工业嵌入式电源设计里“主动保护”和“被动保护”之争的一个缩影。我直接说结论:如果你正在做PLC、运动控制、仪器仪表或者任何24V/12V工业总线供电的板卡,把…

📅 2026/10/7 12:53:04
AI工程化落地:用Genkit构建可测试可部署的Gemini Skills

AI工程化落地:用Genkit构建可测试可部署的Gemini Skills

1. 这不是“技能列表”,而是一套可落地的AI工程化能力体系最近在多个技术社区和内部项目复盘会上,反复听到一个词被高频提起:skills。它既不是简历里泛泛而谈的“熟练掌握Python”“具备良好沟通能力”,也不是招聘JD中模糊的“有A…

📅 2026/10/7 12:48:04
感应加热电源中LC串联谐振的工程本质与实战调校

感应加热电源中LC串联谐振的工程本质与实战调校

1. 这不是教科书里的谐振,是产线上能听见“滋滋”声的真家伙你拆开一台中频感应加热电源的机柜,靠近主功率柜,耳朵凑近散热风道——在IGBT模块高频通断的“哒哒”声间隙里,能听到一种低沉、持续、略带金属质感的“滋…滋…”声。那…

📅 2026/10/7 12:48:04
MORE NEWS

更多资讯

📰

毕业论文文献综述由简单罗列转变为研究缺口推导的写作方法

毕业论文文献综述由简单罗列转变为研究缺口推导的写作方法在硕士与博士研究生毕业论文外审盲审中,“文献综述质量平庸”是评阅意见中最普遍的批评之一。很多评阅专家在意见书中直言不讳地指出:“作者仅仅是对既有文献进行了流水账式的被动罗列&#xff0…

📰

开不完的会,理不清的纪要?这有3大场景实操指南,手把手帮你告别整理噩梦

你是不是也有这样的经历:开了一整天的评审会,录音文件好几百兆,回到家对着音频发愁,手动整理纪要又得熬到凌晨两点;好不容易写完了,第二天领导问你“客户那三个核心需求理出来没有”,你发现纪要…

📰

一次简单越权漏洞复现,带你看懂权限控制缺陷

一次简单越权漏洞复现,带你看懂权限控制缺陷 前言 在 Web 安全漏洞中,越权漏洞属于业务逻辑类漏洞,没有复杂的内存操作、不需要特殊的 Payload,却是各类 SRC、渗透测试、CTF Web 题目里高频出现的一类漏洞。很多新手刚接触 Web 安…

📰

基于WFP的Windows流量监控与转发系统源码实战解析

简介:一套围绕 Windows 过滤平台(WFP)的流量转发与监控实现源码包,面向具备 C/C 基础、希望在内核态或驱动层做网络数据包拦截、转发与统计的开发者,也可用于企业级网络监控系统的二次开发参考。压缩包共 155 个文件&a…

📰

Minecraft模组加载器与Java环境配置指南:从Forge/Fabric到PCL卡死排查

本来我没打算把这段黑历史翻出来,但最近群里有新人第三次问“PCL启动器为什么一直卡在正在开始安装”,我突然想起自己当年为了Minecraft Java版模组加载器这事,在深夜折腾到怀疑人生的样子。这个话题看着冷门,实际上每一个玩mod的…

📰

可选链语法导致白屏:原因排查与修复详解

做前端的人多少都遇到过这种诡异时刻:代码在自己电脑上跑得风生水起,发布上线后被某个用户一句话打回原形——“打不开,白屏”。我前阵子就被一个极其“微小”的语法坑坑过:一个详情弹窗里写了?.可选链语法,结果在低版…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬