Nat*_*ker 4 assembly arm machine-language cortex-m
我正在尝试学习使用 cortex m0 处理器。我有一个 stm32f0 开发板,它可以让我查看每个地址的每一位并轻松上传新的二进制文件。我一直在阅读大量手册,了解许多规则和功能,但仍然不知道程序计数器在重置时从哪里开始,它期望什么类型的参数,我什至不知道如何编写诸如 add 之类的东西或二进制形式的 str/ldr。我读过的手册中是否遗漏了这些基本知识?
它说 m0 有一个完整的降序堆栈,但它似乎表明起点在另一端(0x00000000)。如果向量表也可以用外行的术语来解释,那就太好了。
例如,全尺寸的手臂(cortex-A 等)地址 0x00000000 是自己执行的重置指令,这有点奇怪,您经常看到地址列表,但这就是他们如何做到的。对于 cortex-m,它们不仅使用地址列表,而且硬件设计足够符合 EABI 以允许您将 C 函数名称放在表中,而不必有少量汇编(向量表除外)本身)。
因此,例如使用 gnu 汇编程序。
;@-----------------------
.cpu cortex-m0
.thumb
;@-----------------------
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
Run Code Online (Sandbox Code Playgroud)
在深入研究之前,您需要访问 infocenter.arm.com 上的当前内容,然后在体系结构下,然后在 armv6-m 中获取 armv6-m 体系结构参考手册(ARM ARM for the v6m)。这些链接已经存在很长时间了,但它们自然会发生变化,他们确实称之为并区分了架构参考手册和技术参考手册。体系结构参考手册通常涵盖该体系结构中的系列。TRM 通常涵盖特定核心或特定核心的修订号。可能值得在信息中心页面的其他地方获得它。
我正在查看我认为是 armv6m ARM ARM 的 rev C:ARM DDI 0419C
在文档中搜索“向量表”并找到可能仍然或可能不在同一部分中的内容:
表 B1-4 矢量表格式
该表显示地址空间中偏移量 0 处是 SP_main。这是主堆栈指针的重置值。
然后在表中的偏移量是异常号,字在 arm 世界中是 4 个字节,所以异常号 1 位于地址空间中的偏移量 4,异常 2 位于 8,依此类推。
这总是需要我一段时间才能找到。同样在armv6m arm arm。
B1.5.2 异常编号定义
其中异常编号 1 被重置 2 是 nmi,依此类推。我们关心重置。
所以这意味着在ARMS ADDRESS SPACE中的地址0x00000000处,如果我们选择预加载堆栈地址,我们可以,如果我们希望在引导代码中也设置堆栈,但我们不必,必须在一个地方这样做,但是不是都。
然后在 ARMS 地址空间中的地址 0x00000004 处,我们放置了复位处理程序的地址。
因此,在我的示例中组装、编译和链接代码后,我得到
Disassembly of section .text:
08000000 <_start>:
8000000: 20001000 andcs r1, r0, r0
8000004: 08000041 stmdaeq r0, {r0, r6}
8000008: 08000047 stmdaeq r0, {r0, r1, r2, r6}
800000c: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000010: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000014: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000018: 08000047 stmdaeq r0, {r0, r1, r2, r6}
800001c: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000020: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000024: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000028: 08000047 stmdaeq r0, {r0, r1, r2, r6}
800002c: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000030: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000034: 08000047 stmdaeq r0, {r0, r1, r2, r6}
8000038: 08000047 stmdaeq r0, {r0, r1, r2, r6}
800003c: 08000047 stmdaeq r0, {r0, r1, r2, r6}
08000040 <reset>:
8000040: f000 f80a bl 8000058 <notmain>
8000044: e7ff b.n 8000046 <hang>
08000046 <hang>:
8000046: e7fe b.n 8000046 <hang>
Run Code Online (Sandbox Code Playgroud)
您可以看到,在 gnu 汇编程序的情况下,在标签之前放置 .thumb_func 使该标签成为可能调用的函数或地址,因此需要设置 bx 或 blx 指令,因此需要设置 bx 或 blx 需要正确分支的 lsbit( bl 不在乎)。链接器会自动修复向量表中的地址。例如,在偏移量 0x40 处重置会得到 0x41。
现在为什么不为地址 0x00000000 构建此代码?!那是因为你必须超越 arm 文档,查看芯片供应商文档,arm 不制造芯片,他们制造处理器内核和一些支持它们的逻辑,你去 st 或 nxp 或 ti 或任何人找到其余的特别是地址空间中的 bootflash 在哪里。毫无疑问,在这种情况下,正常启动的 arm 地址空间中地址 0x08000000 处的闪存映射到 0x00000000,一些芯片将有多个启动闪存,并且取决于带(输入引脚连接高或低或各种组合)一个闪存或另一个将永远或在一段时间内映射到地址零。
皮层 m0(和 m1)是基于 armv6m 的,皮层 m3 和 m4 是基于 armv7m 的。巨大的差异后者支持thumb2 对thumb 指令集的扩展(以前未定义的指令成为两个半字指令的前半部分32 位指令不要与32 位arm 模式指令混淆)并且有大约150 条左右的新thumb2 指令添加到 cortex m3 然后 cortex m4 具有浮点单元的一小部分(只有一个浮点大小,可能是单个)以及随之而来的所有指令(基本上是重新定义的 coproessor 指令)。这使 cortex-m0 的生活更轻松,只有 16 位指令(是的,bl 实际上并在文档中定义为两个单独的指令,如果需要,您可以将它们彼此分开编码)。
目前armv6m ARM ARM也包含指令集定义
第 A5 章 Thumb 指令集编码
看着
A6.7.17 CMP (immediate)
Run Code Online (Sandbox Code Playgroud)
(同样,我的部分编号将来可能不会保持不变或匹配,他们的文档通常不会有太大变化或从一个到下一个,但您永远不会知道)。
首先要注意的是编码
Encoding T1 All versions of the Thumb instruction set.
Run Code Online (Sandbox Code Playgroud)
这意味着所有支持拇指的人都支持此指令(从 armv4 到现在)
然后是语法
CMP <Rn>,#<imm8>
Run Code Online (Sandbox Code Playgroud)
统一语法可能与本文档语法不同,也了解 ARM 有自己的工具链,因此定义的语法特定于他们的汇编程序。汇编语言不是标准,它是由汇编程序(解析它的程序)专门定义的。Gnu 汇编器是一个单独的东西,不必遵守这个文档,它大部分都遵守,但 arm 也开始了这个统一语法的东西,允许一定比例的汇编语言组装到拇指、拇指 2 扩展和 arm 指令集而无需重新编写,尽管如果您没有在某处指定三个中的哪一个,您仍然会很快被束缚。
您可以在此指令中看到高位必须是 00101 位 15:11,这就是处理器知道这是立即比较的方式。Rn 是寄存器 r0 到 r7,无论您使用哪个(要访问 r8 到 r15,您必须使用其他 mov 指令以允许 16 位指令,他们必须将大多数指令保留到低 7 个寄存器以保存指令编码中的位) . 然后低 8 位是从 0 到 255 的直接常量值(其他 arm/thumb 立即编码不是那么直接,thumb 与 arm 使用不同的编码,因此您只需阅读手册)。
如果您想查看编码,然后用汇编语言编写,然后反汇编,并让希望调试的工具链为您完成工作,然后尝试对您看到的内容进行逆向工程并将其与手册相匹配,我强烈建议您。头屑是奇怪的地址,但这些都是记录的(虽然不一定像你希望的那样)然后任何与 pc 相关的东西,每当你用它做某事时,pc 前面有两条指令,它不是真的用管道,而是反向兼容性并设置一个标准,即 arm/thumb 标准的两个指令。所以在计算或逆向工程计算 pc 相对地址时,这就是为什么你的数学总是 4 个字节的原因。
与大多数处理器一样,您的程序员或至少是您信任借用其代码的程序员是设置堆栈指针的人。你可以把它放在任何你想要的地方,arm 核心本身,或者 arm 自己不知道芯片供应商将如何实施,编译器也不知道或想知道那里所有可能的芯片,所以你程序员必须告诉工具链,然后工具链告诉 arm 处理器你想要堆栈的位置。传统上,对于降序堆栈,您希望从高地址开始。您首先查看 pop 和 push 指令和伪代码,以查看 arm 首先按寄存器数量递减 4(对于推送),然后写入这些地址,然后在出路时调整 sp。因此,如果您的 ram 以 0x2001FFFF 结束,您可以安全地将堆栈指针放在 0x20020000 处,并且推送的第一件事将在 0x2001FFFC 处。(不是第一件事,但堆栈的最底部在那里)其他非 arm 处理器的工作方式不同并且具有不同的规则,有些您根本无法获得堆栈指针,有些您可以但重置值是正确的,还有一些就像你需要担心的武器。全尺寸的武器你有多个堆栈指针来管理,你也可以让堆栈上升或下降,尽管我不会因为你可以而反对。有些你可以,但重置值是正确的,有些像你需要担心的武器。全尺寸的武器你有多个堆栈指针来管理,你也可以让堆栈上升或下降,尽管我不会因为你可以而反对。有些你可以,但重置值是正确的,有些像你需要担心的武器。全尺寸的武器你有多个堆栈指针来管理,你也可以让堆栈上升或下降,尽管我不会因为你可以而反对。
对于所有 Cortex-M,内存映射中的前两个字(分别位于地址 0 和 4)应该是您的初始堆栈指针和您要开始执行的第一条指令的地址。
通常,您会将堆栈放在可用的最高 RAM 地址处,并使用链接描述文件在地址 4 处插入程序入口点的地址。