在ARM Cortex M0中,第一条指令是什么?

5
我正在学习如何使用cortex m0处理器。我有一个stm32f0开发板,可以查看每个地址的每个位,并轻松上传新的二进制文件。我一直在阅读很多手册,学习很多规则和特性,但仍然不知道程序计数器在复位时从哪里开始,它期望什么类型的参数,也不知道如何以二进制形式编写像add或str/ldr这样的指令。我阅读的手册中是否遗漏了基本知识?
它说m0有一个完整的降序堆栈,但似乎暗示起点在另一端(0x00000000)。如果能用通俗易懂的方式解释向量表,那就太好了。

指令编码可以在《架构参考手册》(也称为ARM ARM - 免费提供,但您必须注册以接受许可证)中找到。 - Notlikethat
谢谢!希望今晚我能成功编写一个闪烁灯程序 :D - Nathan Darker
2个回答

7
完整的处理器 (像 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 .

在开始之前,你需要进入目前的infocenter.arm.com网站,在"architecture"下选择"armv6-m",获取armv6-m架构参考手册(ARM ARM for the v6m)。这些链接已经存在很长时间了,但它们可以随时更改。他们会称之为架构参考手册和技术参考手册,并加以区分。架构参考手册通常涵盖该架构内的系列产品。TRM通常涵盖特定核心或特定核心修订号的特定版本。可能也值得在站点其他地方获取。
我正在查看我认为是armv6m ARM ARM的Rev C: ARM DDI 0419C
在文档中搜索"vector table",并找到可能仍位于同一部分的内容:
表B1-4 向量表格式
该表显示在地址空间偏移量0处为SP_main。这是主堆栈指针的复位值。
然后,在该表中,单词偏移量是异常编号,单词在arm世界中为4字节,因此异常编号1位于地址空间中的偏移量4处,异常2位于8处,依此类推。
我总是要花一些时间才能找到这个。也在armv6m arm arm 中。
引用: B1.5.2 异常编号定义
其中,异常编号1为重置、2为nmi等等。我们关心的是重置。
因此,这意味着在ARMS地址空间的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>

您可以看到,在gnu汇编器的情况下,在标签前放置.thumb_func会使该标签成为一个函数或地址,可以调用bx或blx指令,因此需要设置lsbit,bx或blx需要正确分支(bl不关心)。链接器自动修复向量表中的地址。例如,偏移量0x40的重置获取0x41。

现在为什么不将此代码构建为地址0x00000000?这是因为您必须查看芯片供应商文档,而不仅仅是arm文档。arm不制造芯片,他们制造处理器核心和一些支持逻辑,您去st或nxp或ti或其他地方找到故事的其余部分,特别是引导闪存在地址空间中的位置。毫无疑问,在这种情况下,正常启动时地址空间中的flast位于地址0x08000000处,映射到0x00000000,某些芯片将具有多个引导闪存,根据跳线(输入引脚高电平或低电平或各种组合)之一将被映射到地址零永远或一段时间。

cortex m0(和m1)基于armv6m,cortex m3和m4基于armv7m。巨大的区别后者支持thumb指令集的thumb2扩展(以前未定义的指令成为两个半字指令32位指令的第一半,不要与32位arm模式指令混淆),大约有150个左右的新thumb2指令添加到cortex m3,然后cortex m4具有浮点单元的一部分(仅一个浮点大小,可能是单精度),以及所有随之而来的指令(基本上是重新定义的协处理器指令)。这使得cortex-m0更容易,只有16位指令(是的,bl实际上是并且在文档中被定义为两个单独的指令,如果您愿意,可以将它们分别编码)。

目前,armv6m ARM ARM还包含指令集定义

第A5章Thumb指令集编码

看着

A6.7.17 CMP (immediate)

(我的文件中的章节编号可能在未来不会保持相同或匹配,他们的文档通常不会有太多变化,但是你永远不知道).

首先要注意的是编码方式。

Encoding T1 All versions of the Thumb instruction set.

这意味着所有支持Thumb的人都支持这个指令(从armv4到现在)。
然后是语法。
CMP <Rn>,#<imm8>

统一语法可能与本文档的语法不同,同时请注意ARM有自己的工具链,因此如果定义了语法,则特定于其汇编器。汇编语言不是标准,而是由解析它的程序——汇编器专门定义的。GNU汇编器是一个独立的东西,不必遵循本文档,虽然它大多数时候会这样做,但ARM也开始了这个统一语法的事情,以允许某些百分比的汇编语言汇编到Thumb、Thumb2扩展和ARM指令集,而无需重写,尽管如果您没有在某个地方指定其中三个之一,您很快就会被束缚。

您可以看到,在这个指令中,上位比特必须是00101比特15:11,这是处理器知道这是一个比较立即数的方法。Rn是寄存器r0到r7中的任何一个(要访问r8到r15,您必须使用其他mov指令,以便为16位指令保留大多数指令以节省指令编码中的位数)。然后,低8位是从0到255的直接常量值(其他arm/thumb立即数编码不是那么简单明了,而thumb vs arm使用不同的编码,因此您必须仔细阅读手册)。

我强烈建议,如果您想看到编码,请编写汇编语言,然后进行汇编和反汇编,并让已经调试好的工具链为您完成工作,然后尝试反向工程所见并将其与手册匹配。头皮屑是奇数地址,但这些都有文档记录(虽然不一定如您所希望的那样),然后任何与PC相关的内容,当您对其执行某些操作时,PC是在两个指令之前。它实际上不是用管道处理的,但为了向后兼容性和设置标准,ARM/Thumb标准是提前两个指令。因此,在计算或反向工程计算PC相关地址时,您的数学始终偏离4个字节。

与大多数处理器一样,您作为程序员或者是您信任并借用其代码的程序员来设置堆栈指针。您可以将其放在任何位置,ARM核本身也不知道芯片供应商将要如何实现,编译器也不知道或者不愿意知道所有可能存在的芯片,因此您作为程序员需要告诉工具链,然后工具链再告诉ARM处理器您想要堆栈在哪里。传统上,对于降序堆栈,您希望从高地址开始。首先,您需要查看弹出和推入指令以及伪代码,了解ARM首先按寄存器数量乘以4(对于推入)递减,然后写入这些地址,然后在退出时调整SP。因此,如果您的RAM结束于0x2001FFFF,您可以安全地将堆栈指针放置在0x20020000处,第一个被推入的内容将位于0x2001FFFC处。(不是第一个被推入的内容,但是堆栈的底部就在那里)。其他非ARM处理器的工作方式不同,并且有不同的规则,有些根本无法访问堆栈指针,有些可以访问,但重置值正确,有些像ARM一样需要担心。完整尺寸的ARM处理器有多个堆栈指针需要管理,此外您可以使堆栈向上或向下移动,尽管我不建议这样做。

在GitHub上搜索Thumbulator,我有一个16位Thumb指令模拟器。一旦在那里,我有许多微控制器的示例(例如stm32_samples等),其中包含GNU和LLVM工具链的源文件和make文件。 - old_timer

4

对于所有Cortex-M,内存映射中的前两个字(分别位于地址0和4)应该是您的初始堆栈指针和您想要开始执行的第一条指令的地址。

通常情况下,您会将堆栈放在可用RAM地址最高的位置,并使用链接器脚本在地址4处插入程序入口点的地址。


所以,根据我读到的:正如您所说,第一个单词是堆栈指针,它可以只是地址值而不是指令?(即0x00000000 = 0x3FFFFFFF应该是芯片上SRAM地址的顶部)。第二个地址将是0x00000040 = 0x00000044或类似的东西,指向矢量表之后的地址。然后我基本上可以做任何事情对吗?它期望Thumb指令,因此如果我想将寄存器0加载到一个值中,我只需使用0x00000040 = 0b0010000010101010(直接写入r0)。从这里开始,它会执行该操作,然后查看0x00000042以获取下一条指令吗? - Nathan Darker
不,你想要开始执行的地址在偏移量4处。然后你把你的代码放在那个地址。你真的应该使用C语言,这就是Cortex-M引导序列的全部意义所在。 - Carl Norum
我很固执哈哈。我理解你所说的话,那正是我想表达的,如果我没有表达清楚。我试图从最基础的层面开始学习。是的,C可能更好,但那样我就学不到更多...也不会理解内部发生了什么。 - Nathan Darker
我在尝试使用寄存器时遇到了困难。它是16位指令,这意味着您无法在一条指令中完全加载32位寄存器。我已经使用即时加载命令将字节写入寄存器,但需要在寄存器中具有16位和32位值才能设置gpio寄存器。如何在一个寄存器中获取超过8位的位数? - Nathan Darker
算了,我看到我在查看这个页面时混淆了。这是LDR(寄存器)页面(ARMv6-m参考手册中的A6-143页)。它允许从内存加载32位到寄存器,但是从寄存器中的值获取32位地址。这是一个问题,因为我正在尝试弄清楚如何最初在寄存器中获取32位。它确实说明程序计数器可以用作基地址,这将起作用,但我不知道如何告诉它仅使用3位来选择寄存器时使用PC。猜测我可以先将PC移动到寄存器中。 - Nathan Darker
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接