吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 757|回复: 2
上一主题 下一主题
收起左侧

[原创] Sublime许可证- ARM64架构-第一个校验点详细分析(小白友好,内含前置知识/推理原理)

  [复制链接]
跳转到指定楼层
楼主
LiXieZengHui 发表于 2026-4-18 20:28 回帖奖励
本帖最后由 LiXieZengHui 于 2026-4-18 20:44 编辑

文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口均已做脱敏处理。严正声明禁止用于商业和非法用途,否则由此产生的一切后果与作者本人无关。

有能力请支持正版!!!

有能力请支持正版!!!

有能力请支持正版!!!

碎碎念

过年的时候看到本站大佬分析sublime的文章https://www.52pojie.cn/forum.php?mod=viewthread&tid=2090893&highlight=sublime,分析的那叫一个行云流水,高深莫测;本小白心痒难耐,立马开始分析,谁知在ARM64架构下压根儿找不到switch结构(当时只会依葫芦画瓢),抓耳挠腮了半天,只好作罢。如今实力稍长,借着AI,分析了ARM64架构下的第一个校验点,在此抛砖引玉,还望各位大佬不吝赐教。

目标

绕过许可证校验,无论输入什么都能显示成功。

反汇编

x86/64

我们使用IDA反汇编这个Unix可执行文件,发现一个文件中包含两种CPU架构的指令,我们选择对应的版本,然后进行字符串搜索

DATA XREF: show_invalid_key(void *)+4↑o

说明当前数据在show_invalid_key()函数中偏移量为4的位置被引用

o表示引用的类型是offset

我们继续查看这个函数的引用

在x86/64架构下发现跳转到一个switch结构,那么想要绕过校验,就不要进入这个switch函数即可

可以看到v3来自一个名为apple_fruit的函数

ARM64

Arm64架构下的指令Define Constant Byte

DATA XREF: show_invalid_key(void *)↑o

说明这个字符串在show_invalid_key 中的第一条指令出通过偏移的方式被引用了。我们双击函数名跳转到函数中查看

在函数中我们能够看到先是ADRL ,接着就进行了跳转。

这里的ADRL是一个伪指令,ADRL = ADRP + ADD,编译器为了方便显示,将两个指令融合在一起了。

我们来分析一下这个函数做的事情

ADRL            X1, aThatLicenseKey

ADRL表示计算出aThatLicenseKey这个标签对应的字符串所在的地址,然后将这个地址存放入寄存器X1中。注意,这里仅仅是计算这个字符串的地址,并不是说此时在读取这个字符串,也不是跳转到这个地址。

  • 这里我们再复习一下ADRPADR之间的关系

    ADRP⇒要访问的地址不在当前虚拟内存页上,所以需要跳转到目标虚拟内存页,然后通过ADD添加偏移量跳转到对应的地址。
    ADR⇒要访问的地址离当前PC中的地址很近,不需要范围跳转,所以可以一步到位,直接计算出对应的地址存放入寄存器中。

    总结一下就是:

    • ADRP:目标地址离得远,一条指令的 21 位不够精确定位到字节,所以先粗略定位到目标所在的页,再用 ADD 补上页内偏移,两步到位
    • ADR:目标地址离得近(±1MB 以内),21 位直接当字节偏移用就够了,一步到位

    本质上两者做的事情一样——算出一个相对于 PC 的地址,区别只在于那 21 位立即数的粒度不同:

    21 位表示的是 粒度 范围
    ADR 字节偏移 1 字节 ±1MB
    ADRP 页号偏移 4KB(一页) ±4GB

    ADRP 用页粒度换来了更大的寻址范围,代价是丢失了低 12 位精度,所以需要 ADD 补回来。ADR 不需要这个代价,因为它本身就是字节精度的。

    那么为什么要通过这种地址跳转的方式来读取,而不是直接存储这个地址呢?

    因为ARM64结构下的指令长度固定为32位,而一个内存地址的长度是64位,无法写入指令中,因此,只能通过这种方式曲线救国了。

B               _OUTLINED_FUNCTION_6128

接下来就是Branch到一个公共函数,因为上面将字符串的地址存储到了x1中,这个大概率会读取这个x1并输出。

我们继续查看这个show_invalid_key 的交叉引用

显示调用位置处于当前函数上方,对应的指令为ADR X0, __show_invalid_key

这个函数我们找不到对应的交叉引用了,然后这个函数看起来就仿佛是孤零零的长出来的一样。。。

那么此时我们获取只能借助动态调试的方法来查找引用了

动态调试定位校验点

前期准备

App重签名

codesign --deep --force --sign - "xxx.app" 这里有一点要注意,顺序不要写错,--sign 后面必须紧跟着证书,我们没有证书就使用- 来代替,表示使用临时证书

然后打开App,查找对应的进程

显然是最后一个,我们将调试器附加到对应的进程之上

lldb -p 9290

或者我们直接lldb xxx.app && run 也可以打开调试,这样做有一点好处,通过这种方法打开的Ap是不会启动ASLR,默认就是基地址!!!

ASLR

那么想要知道谁调用了show_invalid_key(),我们肯定是要在内存中找到这个函数并打印调用堆栈,所以我们需要先知道这个函数在内存中的真实地址,也就是说我们要先获取ASLR(Address Space Layout Randomization)偏移量

我们使用image list -f -o xxx

OK,我们现在知道模块的整体偏移是0x0000000000aac000,所以我们只需要在IDA中查看静态偏移,两者相加即可知道该函数在内存中的地址

所以在内存中的真实地址是0x00000001001D2D40+0x0000000000aac000=0x100C7ED40

如果对计算结果不自信,我们可以使用vmmap来查看当前进程每个段在内存中的地址

我们可以看到text段的地址范围是在0x100aac000-0x101060000 之间,我们的计算结果符合事实。

接下来就是在函数的地址上添加断点,我们使用b Address

确实是我们要寻找的函数

接下来我们放开App(continue)开始跑起来,进入许可证判定,然后触发断点

Process 9290 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x0000000100c7ed40 sublime_text`show_invalid_key(void*)
sublime_text`show_invalid_key:
->  0x100c7ed40 <+0>: adrp   x1, 737
    0x100c7ed44 <+4>: add    x1, x1, #0x67e ; "That license key doesn't appear to be valid.\n\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines."
    0x100c7ed48 <+8>: b      0x100f2df04    ; OUTLINED_FUNCTION_6128

sublime_text`show_revoked:
    0x100c7ed4c <+0>: adrp   x1, 737
Target 0: (sublime_text) stopped.

LLDB 调试器输出格式解析


进程与线程信息

  • Process 9290 stopped
    • 进程 ID 为 9290 的进程已停止执行。这是被调试的 Sublime Text 进程。
  • thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    • 表示这是当前活跃线程
    • thread #1 — 线程编号为 1
    • queue = 'com.apple.main-thread' — 该线程运行在 macOS 的主线程 GCD 队列
    • stop reason = breakpoint 3.1 — 停止原因是命中了第 3 个断点的第 1 个位置(LLDB 中一个断点可以解析到多个地址,用 .1 表示第一个匹配位置)
  • *`frame #0: 0x0000000100c7ed40 sublime_text'show_invalid_key(void)`**
    • frame #0 — 当前栈帧编号为 0(即栈顶,正在执行的函数)
    • 0x0000000100c7ed40 — 当前指令的内存地址
    • sublime_text — 所属的可执行文件/模块名称
    • show_invalid_key(void*) — 当前函数名及其参数签名,这是一个接受 void* 参数的 C++ 函数,功能是显示无效许可证密钥的提示

反汇编代码

这部分是函数 show_invalid_key反汇编输出,使用的是 ARM64 指令集

  • > 0x100c7ed40 <+0>: adrp x1, 737
    • > 表示程序计数器(PC)当前停在这一行
    • 0x100c7ed40 — 指令的绝对内存地址
    • <+0> — 相对于函数起始地址的偏移量(+0 表示这是函数的第一条指令)
    • adrp x1, 737Address of Page 指令,将第 737 页的基地址加载到寄存器 x1。这是 ARM64 中用于构造大地址常量的第一步(页对齐,4KB 粒度)
  • 0x100c7ed44 <+4>: add x1, x1, #0x67e
    • <+4> — 偏移 4 字节(每条 ARM64 指令固定 4 字节)
    • add x1, x1, #0x67e — 将页内偏移 0x67e 加到 x1,与上一条 adrp 配合,完成完整地址的构造
    • 分号后面是 LLDB 自动解析出的字符串内容"That license key doesn't appear to be valid.\\n\\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines." — 这就是 Sublime Text 弹出的无效许可证提示信息
  • 0x100c7ed48 <+8>: b 0x100f2df04 ; OUTLINED_FUNCTION_6128
    • b无条件跳转(Branch)指令
    • 跳转到地址 0x100f2df04,LLDB 标注其符号名为 OUTLINED_FUNCTION_6128
    • 这是编译器的 Machine Outlining 优化:将多个函数中重复的指令序列提取为共享的"outlined function",以减小二进制体积。这里跳转过去的代码很可能是调用弹窗/对话框显示逻辑

下一个函数的开头

  • sublime_text'show_revoked:
    • 这是紧邻 show_invalid_key 之后的另一个函数 show_revoked 的起始标签
    • <+0>: adrp x1, 737 — 同样在加载一个字符串地址,推测是显示"许可证已被撤销"的提示

最后一行

  • Target 0: (sublime_text) stopped.
    • Target 0 — LLDB 中的调试目标编号
    • (sublime_text) — 目标程序名
    • stopped — 确认目标已停止

整体结构总结

┌─ 进程/线程状态信息
│   Process ID, Thread, Stop Reason
│
├─ 栈帧信息
│   Frame #, 地址, 模块名`函数名(参数)
│
├─ 反汇编代码
│   [->] 地址 <偏移>: 指令  操作数  [; 注释/字符串]
│   ...
│
└─ Target 状态
    Target N: (模块名) stopped.
->  0x100c7ed40 <+0>: adrp   x1, 737
    0x100c7ed44 <+4>: add    x1, x1, #0x67e ; "That license key doesn't appear to be valid.\n\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines."
    0x100c7ed48 <+8>: b      0x100f2df04    ; OUTLINED_FUNCTION_6128

-> 表示PC正停在这一行,0x100c7ed40表示当前内存中的真实地址,也就是我们下断点的地址,<+0> 表示相对于函数起始地址的偏移量,此处的0表示这是函数开头的第一条指令

adrp   x1, 737  

adrp ⇒Address of Page

整条指令表示取当前PC所在页的基地址,加上737页的偏移,结果存入x1寄存器。

这里引入一个概念,“立即数”,立即数指的就是直接写在指令里的常量数值,不需要从内存或寄存器中读取。这里的adrp的立即数存在位数限制,只能是一个21位立即数,刚好覆盖±4GB范围内的页。

这条指令的计算过程

  1. 当前PC=0x100c7ed40
  2. 当前PC所在页的基地址=0x100c7e000 (Arm64的内存已4KB(0x1000)为一页)
  3. 加上737页=0x100c7e000+737*0x1000=0x100F5F000
  4. 0x100F5F000存入寄存器x1

为什么要这么做???

因为Arm64架构的指令长度固定为32位,放不下一个完整的64位地址,所以先使用adrp定位到当前页,再使用add加上内存偏移(也就是页内偏移),相当于分了两步走,第一步先跳转到对应的页,第二部再在页内跳转到对应的内存地址

0x100c7ed44 <+4>: add    x1, x1, #0x67e ; "That license key doesn't appear to be valid.\n\nPlease check that you have entered all lines from the license key, including the BEGIN LICENSE and END LICENSE lines."

我们能够看到第二条指令的地址位于0x100c7ed44,与第一条指令的地址相差0x4,也就是4,那么相差4个什么呢?自然是相差4个字节,因为内存中最小的寻址单位就是字节,所以这里相差的就是4字节,那么为啥偏偏是4字节呢?因为ARM64架构下的指令是定长指令,每条指令的长度为32位,也就是4个字节。

然后我们看这是一条add指令,这条add指令有三个参数,分别对应着目标寄存器、源操作数1、源操作数2。这里的0x67e就是一个立即数,只要前面出现#,那么一定表示一个立即数

我们看到这句话后面还出现了字符串,这是个反汇编器自动添加的,反汇编器查看add指令计算完成后的结果,发现是一个字符串,然后就输出了

我们可以使用命令手动输出并查看这个字符串

x/s 0x100F5F67E

0x100c7ed48 <+8>: b      0x100f2df04    ; OUTLINED_FUNCTION_6128

最后一条指令,是一个b指令,表示跳转到地址0x100f2df04,对应一个公共函数,此处的b表示无条件跳转且不保存地址,也就是说跳转过去之后就不回来了。

直接调试

我们查看这个函数对应的地址,并在lldb中添加断点然后触发

可以看到当前的栈顶就是我们当前的函数

输出堆栈信息(bt)

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x0000000100c7ed40 sublime_text`show_invalid_key(void*)
    frame #1: 0x0000000100d6eaac sublime_text`-[WorkQueueCallback processItems:] + 128
    frame #2: 0x0000000189f57acc Foundation`__NSThreadPerformPerform + 264
    frame #3: 0x00000001886f0bc0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
    frame #4: 0x00000001886f0b54 CoreFoundation`__CFRunLoopDoSource0 + 172
    frame #5: 0x00000001886f08c0 CoreFoundation`__CFRunLoopDoSources0 + 232
    frame #6: 0x00000001886ef4e4 CoreFoundation`__CFRunLoopRun + 820
    frame #7: 0x00000001887c1be0 CoreFoundation`_CFRunLoopRunSpecificWithOptions + 532
    frame #8: 0x00000001954c4560 HIToolbox`RunCurrentEventLoopInMode + 320
    frame #9: 0x00000001954c77e4 HIToolbox`ReceiveNextEventCommon + 272
    frame #10: 0x000000019565113c HIToolbox`_BlockUntilNextEventMatchingListInMode + 48
    frame #11: 0x000000018d1c71a4 AppKit`_DPSBlockUntilNextEventMatchingListInMode + 228
    frame #12: 0x000000018cb1b084 AppKit`_DPSNextEvent + 576
    frame #13: 0x000000018d6b069c AppKit`-[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 688
    frame #14: 0x000000018d6b03a8 AppKit`-[NSApplication(NSEventRouting) nextEventMatchingMask:untilDate:inMode:dequeue:] + 72
    frame #15: 0x000000018cb0e13c AppKit`-[NSApplication run] + 368
    frame #16: 0x0000000100d76014 sublime_text`px_run_event_loop() + 236
    frame #17: 0x0000000100c763cc sublime_text`main + 11184
    frame #18: 0x0000000188277da4 dyld`start + 6992

我们来分析一下堆栈信息

#18 程序启动,使用macOS的动态链接起dydl加载所有的动态库,然后跳到#17main,并执行main函数内部偏移11184字节的位置
#16 开始启动事件循环
#15-#12 对应AppKit,是macOS事件循环机制
#11-#4 macOS底层事件处理 HIToolbox+CoreFoundation
#1 sublime_text进程回调
#0 命中断点

这里我们发现是一个回调,回调说明是异步,也就是说我们下断点的时机不对,验证逻辑在后台执行,结果通过工作队列回到主线程。

我们再回到IDA中观察

这里的指令使用的是ADR x0,pointer_func ,这里用的是ADR而不是直接调用,说明结果已经在上面计算完成了,这里应该就是执行错误提示的位置,我们再往上寻找调用是没有道理的。。。我们其实应该查找的是一个if或者switch 结构。这里存在好几个并列的错误处理,那么我们要寻找的大概率是一个switch结构。

我们仔细观察,发现一个有趣的现象,所有的错误函数最后都会跳转到相同的地址,应该对应的就是之前看到的那个公共函数

那我们找到第一次跳转,然后再往前找找,应该就是判断的点位了

我们可以使用动态调试的方法来查看一下!!!

小声哔哔:经验告诉我们,这里大概率就是开始switch了,为什么呢?因为如果仅仅只是进行跳转,编译器一定会优先采用B而非BR,因为B的开销更小,速度更快;而且BR多用于跳转到某一个虚函数,因为具体跳转到哪个子类,只有在代码运行的时候才能确定,这种情况下只能存储在寄存器中然后跳转过去。

好,我们装作不知道,我们先运行一下动态调试来验证一下,我们直接对这个BR打点,断住之后读一下寄存器X10即可

OK,我们看到寄存器X10中存储的地址是0x00000001001d2c50 ,因为我们现在没开ASLR,所以我们不需要转换成静态地址,我们直接在IDA中进行跳转,

很好,非常好,跳转到了对应的错误处理函数。说明判断就在上方,而且这里就是一个switch

我们接下来分析一下上方的判定指令,找一下哪里在进行判定

__text:00000001001D2A9C                 BL              _OUTLINED_FUNCTION_12005
__text:00000001001D2AA0                 CMP             W0, #0
__text:00000001001D2AA4                 CSET            W9, EQ
__text:00000001001D2AA8                 LDR             X8, [X20,#0x268]
__text:00000001001D2AAC                 STRB            W9, [X8,#5]
__text:00000001001D2AB0                 CBZ             W0, loc_1001D2B34
__text:00000001001D2AB4                 SUB             W8, W0, #1
__text:00000001001D2AB8                 ADRL            X9, unk_1004DE8C6
__text:00000001001D2AC0                 ADR             X10, loc_1001D2AD0
__text:00000001001D2AC4                 LDRB            W11, [X9,X8]
__text:00000001001D2AC8                 ADD             X10, X10, X11,LSL#2
__text:00000001001D2ACC                 BR              X10

关于w0/x0寄存器,这里提一嘴,这俩共用同一个物理寄存器,但是x的长度是64位而w的长度是32位,且w对应的是低32位,如果往w中写入数据,则CPU会把高(此处高低等同数学中的高位&低位)32位全部清零,这就是零扩展。

那么根据上面的分析,这里的CBZ肯定相当十分的重要

我们先不急着下一步,我们其实可以跳转到伪代码中进行校验

OK,那么显然我们只需要阻住if(v5) 成立即可,也就是需要Patch这一条语句

__text:00000001001D2AB0                 CBZ             W0, loc_1001D2B34

根据上面的分析,当公共函数运算结果为0时会进行跳转,那么显然我们让其无条件跳转即可

其实我们还可以看汇编来进行分析,如果公共函数执行结果为0,就跳转loc_1001D2B34这个地址,我们跳转到这个地址看一下

这个是符合上面的伪码的。另一种思路就是,我们看到BR X10 在if条件成立的情况下执行,显然就不能让if成立,自然要在if这里无条件跳转掉,因为顺序执行的话就进入各种错误处理函数了~

所以我们选择将CBZ 修改为B

我们知道B指令的机器码形如0001 01nn nnnn nnnn nnnn nnnn nnnn nnnn

在ARM64架构下,一条指令长度严格限制为32位,且内存中按照小端序存储,所以CBZ这条指令实际上应该是34 00 04 20

我们要修改为B指令,B指令中存储的是偏移量,所以我们需要计算目标地址与当前地址的偏移量,offset = 0x1001D2B34 - 0x1001D2AB0 = 0x84 ,所以这里的偏移量对应的是0x84字节,但是B指令的偏移量并不是指地址的偏移量,而是指指令的偏移量,可以理解为跳转到n条指令后的那条指令继续执行,那么在ARM64架构下的指令长度为4字节,所以真正的偏移为0x84/ 4 = 0x21

所以最后就是B指令的机器码+指令偏移数量

0x14000000 + 0x21 = 0x14000021

然后按照小端序排列,最终这条指令对应的十六进制显示为21 00 00 14

转换成伪代码之后可以发现讨厌的BR X10不见了~

我们保存更改,然后重签名后打开,测试结果如下

杂&知识

vtable-虚函数表

我们对错误处理函数所在的大函数查询交叉引用

那么问题来了,为什么会从__text段跳转到__const段呢?

众所周知,text段存放的是可执行的机器指令,也就是CPU实际执行的代码,可读+可执行,而const段只存放常量只读数据,可读,不可写,不可执行,这些数据是程序在编译阶段就确定&运行时不会改变的数据。那么为什么会从const段关联跳转到text段呢?

这是何意?我们仔细观察,发现在对应的const段中出现了

PS:我问AI的,原谅我稀烂的C++和底层知识

这是典型的虚函数表

那么我们就可以知道,这里实际上是一个多态,程序需要执行lambada中的cal方法,然后这个类存在继承,接下来就是查找vtable,查看这个方法的具体实现,获取到这个实现的地址之后再跳转到对应的地址,所以在我们的视角里就是先跳转到const段中的虚函数表然后再跳转到函数的具体实现。

关于 NOP 与内存对齐的体系结构化表述

1. NOP 指令的定义与微架构行为

NOP (No Operation) 在 AArch64 指令集中被定义为一种显式空操作伪指令。从微架构角度看,该指令在流水线(Pipeline)的译码阶段即被识别。它虽然会占用一个发射槽(Issue Slot)并消耗一个时钟周期(Clock Cycle),但不会触发执行单元(ALU)的状态变更,亦不会对通用寄存器堆(Register File)或状态寄存器(NZCV)产生副作用。

2. 内存寻址的物理约束:4字节对齐 (Alignment)

ARM64 架构采用固定长度(Fixed-length)指令设计,所有指令字严格限制为 32 bits (4 Bytes)。处理器的取指单元(Instruction Fetch Unit)基于字对齐边界(Word-aligned boundaries)进行内存访问。这意味着 PC(程序计数器)的最低两位在物理层面上被预期为零。

3. 异常起因:内联数据导致的地址偏移 (Misalignment)

当开发人员或编译器在代码段(.text)中嵌入非 4 字节倍数长度的常量数据(例如通过 DCB 指令定义的 8-bit 字节数据)时,地址计数器(Location Counter)会产生非对称偏移。若不进行干预,后续指令的起始地址将落在非对齐边界(如 4n+1,4n+2 或 4n+3)。CPU可以读取这种存在偏移的地址,但是无法正常解析,因为物理电路的限制,会导致强行从低位读取,得到的即是两条破碎的指令。

4. NOP 指令的补偿机制 (Padding Strategy)

为了修正上述地址偏移,编译器会采用填充策略(Padding)。当数据段结束后,通过插入 1 至 3 个字节的填充物,将内存指针强制推移至下一个最近的 4n 边界。

CPU  取指-译码-执行(Fetch-Decode-Execute) 循环

PC是一个寄存器,容量是8字节,内部存储的是指针,而CPU可以获取通过这个指针读取对应的内存中的数据,长度是4字节,且只能是4字节,因为PC指向的永远都是指令,而指令的长度永远都是4字节。然后CPU能够解码并执行这个指令,这个指令中涉及到地址相关的,CPU也能够操作,如果是B或者BL之类的,CPU会修改PC的值,如果不是,则PC自增+=4。

CPU先读取一个地址,获取里面的数据,地址长度是8字节,数据的长度是未知的。如果这个地址对应的数据是一条指令,那么这个数据大小一定就是4字节,CPU获取到指令之后开始执行指令,然后又涉及到读取新的地址,获取新的数据和指令,如此循环往复,就是在运行程序

CPU 就是一个不断"取指令→执行→取下一条"的循环机器,指令是 4 字节的命令,地址是 8 字节的门牌号,两者各司其职。

8字节的地址中存储着4字节的指令,而PC中存储着8字节的地址,CPU读取PC中的地址,获取地址中的指令并执行,然后根据指令的类型PC自增或被CPU修改。


1. 指令获取:4 字节的固定节拍

正如你所说,你的 PC (Program Counter) 寄存器虽然是个 8 字节(64 位) 的庞然大物,但它每次指向内存时,带回来的“包裹”永远只有 4 字节


2. 指令解码:翻译官上线

当这 4 字节的指令进入 CPU 后,它会被送入 指令译码器 (Instruction Decoder)

  • 指令内容:这 32 位(4 字节)里包含了“动作编码”、“寄存器编号”以及一些“小数字”(立即数)。

3. 指令执行:两条不同的路

CPU 会根据指令的类型决定 PC 的命运:

情况 A:普通指令(如加法、内存读写)

如果指令是 ADD X0, X1, X2 或者 LDR X0, [X1]

  • PC 的命运PC = PC + 4
  • 操作方法:因为 ARM 指令固定 4 字节,所以 PC 只需要简单地加上 4,就能精准地指向下一条指令的起始位置。

情况 B:分支指令(如 B, BL, RET)

如果指令是 B <label>(跳转)或者 BL <function>(调用函数)。

  • PC 的命运PC = 目标地址
  • B (Branch)Branch 意思是“分支”或“跳跃”。
  • BL (Branch with Link)Link 意思是“链接”。它不仅会修改 PC 跳过去,还会把当前的 PC+4 存到另一个叫 LR 的寄存器里。
  • 为什么要存 LR:这样当函数运行完了(执行 RET),CPU 只要把 LR 的值再塞回 PC,就能“原路返回”继续干刚才没干完活了。

CPU 的工作循环:

1. 看 PC 的值 → 比如 0x100c7ed40
2. 去内存 0x100c7ed40 取 4 字节 → 得到一条指令
3. 解码并执行这条指令
4. PC 自动 +4 → 变成 0x100c7ed44
5. 回到第 1 步

Q:那么现在的CPU是64位的,代表一次最多处理64位数据,那么如果我要修改一个地址对应的超长字符串我应该怎么办呢?

A:其实问出这个问题说明对概念理解的不透彻,CPU并不会把这个字符串一次性取出来然后撑爆自己。而是CPU 每次读取 8 字节到寄存器,修改完,写回内存;然后把指针增加 8,再重复这个动作,直到修改完成。

Word

DCQ(Define Constant Quad-word)

然后我们再来探究一下-Word这个概念,众所周知,CPU与内存之间存在一根数据总线(Data Bus),这条总线的宽度代表了CPU一次性能从内存中搬运多少数据。

在最早期16位CPU的时代,一个Word = 16 Bit = 2 Byte,但是随着CPU的升级,Word的值并没有跟着升级,结果就演变成了现在的WORDDWORDQWORD,而ARM这边WORD最初的定义是32 Bit = 4 Byte,然后对于ARM64来说就出现了Double-Word,所以上面定义函数指针的时候分配内存用的是DCQ,这里沿用了部分Inter的命名方式,记大小就行。

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
ehu4ever + 1 + 1 谢谢@Thanks!
laozhang4201 + 1 + 1 热心回复!
zhczf + 1 + 1 我很赞同!
LLuis + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
mhfymn 发表于 2026-4-19 10:29
牛,点赞分享
3#
飘浮 发表于 2026-4-19 21:00
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-20 13:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表