重置后的ARM PC值

标签 arm microcontroller stm32 cortex-m3 thumb

我是MCU的新手,试图弄清楚基于arm(Cortex M3-M4)的MCU的启动方式。由于启动是针对任何SOC的,因此我以STM的硬件板为例进行了案例研究。

Board: STMicroelectronics – STM32L476 32-bit.



在此板中,当引导模式为(x0)“从用户闪存启动”时,该板将0x0000000地址映射到闪存地址。在闪存上,我已将二进制文件的前4个字节粘贴到向量表的第一个条目esp上。现在,如果按复位按钮,ARM文档将说PC值将设置为0x00000000

CPU通常基于PC -> PC + 1循环执行指令流。在这种情况下,如果我看到PC值指向esp,这不是指令。 Arm CPU如何不使用该指令地址的逻辑,而是跳转到值0x00000004的值存储呢?

或者是这种情况:
重置会产生特殊的硬件中断,并导致PC值等于0x00000004,如果是这种情况,为什么Arm文档说它将PC值设置为0x00000000呢?

引用:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3761.html

What values are in ARM registers after a power-on reset? Applies to: ARM1020/22E, ARM1026EJ-S, ARM1136, ARM720T, ARM7EJ-S, ARM7TDMI, ARM7TDMI-S, ARM920/922T, ARM926EJ-S, ARM940T, ARM946E-S, ARM966E-S, ARM9TDMI

Answer Registers R0 - R14 (including banked registers) and SPSR (in all modes) are undefined after reset.

The Program Counter (PC/R15) will be set to 0x000000, or 0xFFFF0000 if the core has a VINITHI or CFGHIVECS input which is set high as the core leaves reset. This input should be set to reflect where the base of the vector table in your system is located.

The Current Program Status Register (CPSR) will indicate that the ARM core has started in ARM state, Supervisor mode with both FIQ and IRQ mask bits set. The condition code flags will be undefined. Please see the ARM Architecture Manual for a detailed description of the CPSR.

最佳答案

cortex-m的启动方式与传统的全尺寸内核启动方式不同。那些至少用于您指出的复位操作是从地址0x00000000(或断言,如果断言的话)中获取的第一条指令,将其称为PC值是不公平的,因为此时PC有点漏洞,有多个程序计数器在r15中产生了一个伪造的东西,一个领导取回,一个进行了预取,但实际上都不是程序计数器。无论如何,没关系。

cortex-m如armv7-m文档中所述(对于m3和m4,对于m0和m0 +,请参见armv6-m,尽管到目前为止它们都以相同的方式启动)。这些使用向量表而不是指令。 CORE读取地址0x00000000(如果声明了 strip ,则为备用),并将32位值加载到堆栈指针寄存器中。它读取地址0x00000004,它检查lsbit(也许不是所有的内核都这样做),如果这是一个有效的大拇指地址,则将lsbit剥离(使其为零),并开始在该地址处获取复位处理程序的第一条指令,因此如果您的闪光灯以

0x00000000 : 0x20001000
0x00000004 : 0x00000101

cortex-m会将0x20001000放入堆栈指针,并从地址0x100提取第一条指令。 Thumb指令为16位,thumb2扩展为两个16位部分,它不是x86程序计数器是针对具有32位指令的完整尺寸处理器对齐的,它在对齐地址0x0000、0x0004、0x0008上获取它不会递增pc <= pc + 1;对于拇指模式或拇指处理器,它是pc = pc +2。但是,取指令并不一定是单指令事务,对于完整大小的取指令,每个事务可以取4或8个字,这是技术引用手册中记录的cor-ms。有些可以一次被编译或捆绑为16位,或者一次为32位。因此,无需谈论或考虑获取pc = pc + 1的执行循环,即使在现在的x86中也没有意义。

公平地说,军备文件通常是好的,与许多其他方面相比,更好的方面不是最好的。与完整尺寸的 ARM 异常表不同,cortex-m文档中的矢量表未尽如人意,可能已经/应该做过与完整尺寸类似的操作,但显示它们是矢量而不是指令。但是在armv6-m和armv7-m的体系结构引用手册中有此说明(我也假设armv8-m但没有看,上周有一些零件,但是板子还没有在这里,很快就会知道。 )。无法在该手册中查找类似reset之类的单词,必须查找中断或未定义或硬件故障等。

编辑

不用担心处理器是如何开始提取的,您可以将它们添加到设计中的任何任意地址,然后执行指令确定下一个地址和下一个地址,等等。

也了解不像说x86或microchip pic或avrs等,核心和芯片是两家不同的公司。即使在相同的公司设计中,但肯定在IP与已知总线之间有明确划分的地方,ARM CORE会读取AMBA/AXI/AHB总线上的地址0x00000004,芯片供应商可以将该地址映射为许多不同的地址根据他们的需要放置,在这种情况下,使用stm32可能实际上在0x00000000处没有任何内容,因为其文档暗示基于引导引脚,它们将其映射到内部引导加载程序,或者将其映射到用户应用程序在0x08000000处(或者在大多数情况下, stm32's,如果有一个异常(exception),那还不错,我还没有看到),所以当捆绑这种方式并且逻辑将那些地址镜像时,您将在0x00000000和0x08000000、0x00000004和0x08000004上看到相同的32位值,依此类推地址空间。这就是为什么即使链接0x00000000在某种程度上仍然有效(直到您达到了可能小于应用程序闪存大小的限制),您仍会看到大多数人都链接了0x08000000,而硬件则负责其余的工作,因此您的表真的想要看起来像
0x08000000 : 0x20001000
0x08000004 : 0x08000101

对于一个stm32,至少到目前为止我已经看到了几十个。

处理器读取0x00000000,将其镜像到应用程序闪存中的第一项,找到0x20001000,然后读取0x00000004,将其镜像至应用程序闪存中的第二个字,并获取0x08000101,这导致从0x08000100进行提取,现在我们从正确的完全映射的应用程序闪存地址空间。只要您不更改镜像,我就不知道您是否可以在stm32上使用(nxp芯片可以,而且我不了解ti或其他品牌)。 VTOR寄存器位于其中的某些cortex-m内核是可更改的(其他内核固定为0x00000000,并且您无法更改),对于stm32,您不需要将其更改为0x08000000,至少我知道的所有内核。仅当您可能时主动更改零地址空间的镜像时,或者说您拥有自己的引导加载程序,并且也许您的应用程序空间为0x08004000且该应用程序需要自己的向量表时,才可以使用它。那么您可以使用VTOR或构建引导加载程序向量表,以便它运行读取0x08004000处的向量并将其分支的代码。恩智浦和过去肯定拥有ARMV7TDMI内核的其他内核,可以让您更改地址零的镜像,因为那些较早的内核没有可编程的向量表偏移寄存器,可帮助您解决其芯片设计中的问题。带有VTOR的新型ARM内核消除了这种需求,并且随着时间的流逝,芯片供应商可能根本不再理会...

编辑

我不知道您是否拥有发现板或核子,我认为后者是不可用的(希望我知道有人想要一个。)和/或我已经有一个人,并将其埋在抽屉里,我从来没有去过)。

所以这是一个最小的程序,您可以在stm32上尝试
.cpu cortex-m0
.thumb
.globl _start
_start:
.word 0x20000400
.word reset
.word loop
.word loop
.thumb_func
loop: b loop
.thumb_func
reset:
    ldr r0,=0x20000000
    mov r2,sp
    str r2,[r0]
    add r0,r0,#4
    mov r2,pc
    str r2,[r0]
    add r0,r0,#4
    mov r1,#0
top:
    str r1,[r0]
    add r1,r1,#1
    b top

build
arm-none-eabi-as so.s -o so.o
arm-none-eabi-ld -Ttext=0x08000000 so.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf -O binary so.bin

过去十年来,这应该使用binutils的arm-linux-whatever-或其他arm-whatever-what工具构建。

在使用二进制文件之前,请务必对反汇编进行检查,不要对您的芯片进行砌块(使用stm32,有一种方法可以使其变砖)
08000000 <_start>:
 8000000:   20000400    andcs   r0, r0, r0, lsl #8
 8000004:   08000013    stmdaeq r0, {r0, r1, r4}
 8000008:   08000011    stmdaeq r0, {r0, r4}
 800000c:   08000011    stmdaeq r0, {r0, r4}

08000010 <loop>:
 8000010:   e7fe        b.n 8000010 <loop>

08000012 <reset>:
 8000012:   4805        ldr r0, [pc, #20]   ; (8000028 <top+0x6>)
 8000014:   466a        mov r2, sp
 8000016:   6002        str r2, [r0, #0]
 8000018:   3004        adds    r0, #4
 800001a:   467a        mov r2, pc
 800001c:   6002        str r2, [r0, #0]
 800001e:   3004        adds    r0, #4
 8000020:   2100        movs    r1, #0

08000022 <top>:
 8000022:   6001        str r1, [r0, #0]
 8000024:   3101        adds    r1, #1
 8000026:   e7fc        b.n 8000022 <top>
 8000028:   20000000    andcs   r0, r0, r0

反汇编程序不知道向量表不是指令,因此您可以忽略它们。
08000000 <_start>:
 8000000:   20000400
 8000004:   08000013
 8000008:   08000011
 800000c:   08000011

08000010 <loop>:
 8000010:   e7fe        b.n 8000010 <loop>

08000012 <reset>:

它是否在0x08000000处启动向量表,请检查。我们的堆栈指针初始值是0x00000000,是的,我们有工具放置的复位向量。 thumb_func告诉他们以下标签是某些代码/函数/过程/whatever_not_data的地址,因此他们在此处为我们提供了一个标签。我们的重置处理程序位于地址0x08000012,因此我们想在向量表中看到0x08000013,请检查。为了演示起见,我又扔了几个,将它们发送到地址0x08000010的无限循环中,因此向量表应为0x08000011,请检查。

因此,假设您没有发现的核子板,则可以将so.bin文件复制到插入时显示的拇指驱动器。

如果您现在使用openocd通过STLink接口(interface)连接到开发板上,则可以看到它正在运行(详细信息留给读者看一下)
Open On-Chip Debugger
> halt
stm32f0x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000022 msp: 0x20000400
> mdw 0x20000000 20
0x20000000: 20000400 0800001e 0048cd01 200002e7 200002e9 200002eb 200002ed 00000000 
0x20000020: 00000000 00000000 00000000 200002f1 200002ef 00000000 200002f3 200002f5 
0x20000040: 200002f7 200002f9 200002fb 200002fd 
> resume
> halt
stm32f0x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08000022 msp: 0x20000400
> mdw 0x20000000 20
0x20000000: 20000400 0800001e 005e168c 200002e7 200002e9 200002eb 200002ed 00000000 
0x20000020: 00000000 00000000 00000000 200002f1 200002ef 00000000 200002f3 200002f5 
0x20000040: 200002f7 200002f9 200002fb 200002fd 

因此我们可以看到堆栈指针的预期值为0x20000400
0x20000000: 20000400 0800001e 0048cd01

程序计数器不是什么神奇的东西,他们必须在某种程度上伪造它以使指令集起作用。
 800001a:   467a        mov r2, pc

根据指令集中的定义,该指令中使用的pc值比该指令的地址前面两个指令,因此0x0800001A + 4 = 0x0800001E这就是我们在内存转储中看到的内容。

第三项是一个计数器,表明我们正在运行,恢复和停止表明该计数一直在继续
0x20000000: 20000400 0800001e 005e168

这样就说明了向量表,初始化堆栈指针,复位向量,代码执行开始的地方,pc在程序中某个位置的值,以及程序正在运行。

.cpu cortex-m0使其构建了与cortex-m系列最兼容的程序,而mov r0,= 0x20000000在作弊,您在注释中发布了相同的功能,它说我想将blah的地址加载到寄存器中标签只是一个地址,它们只允许您放置一个地址= _estack是标签的地址= 0x20000000只是一个被视为地址的数字(地址也就是数字,这对他们来说没有什么神奇的)。我可以通过轮类完成较小的立即数,也可以明确地完成PC的相对负载。在这种情况下习惯的力量。

编辑2

在试图让程序员理解芯片是逻辑的过程中,只有一部分是软件/指令驱动的,即使在这种逻辑中,逻辑本身所做的事情也比软件指令本身所指示的要多。您想从内存中读取指令来要求处理器执行该指令,但是在实际的芯片中,要实际执行该指令涉及许多步骤,无论是否进行了微编码(ARM未进行微编码),都有状态机执行各个步骤执行这些任务中的每一个。从寄存器中获取值,计算地址,执行存储事务(这是几个单独的步骤),获取返回值并将其放置在寄存器文件中。
.thumb
.globl _start
_start:
.word 0x20001000
.word reset
.word loop
.word loop
.thumb_func
loop: b loop
.thumb_func
reset:
    ldr r0,loop_counts
loop_top:
    sub r0,r0,#1
    bne loop_top
    b reset
.align
loop_counts: .word 0x1234


00000000 <_start>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000013    andeq   r0, r0, r3, lsl r0
   8:   00000011    andeq   r0, r0, r1, lsl r0
   c:   00000011    andeq   r0, r0, r1, lsl r0

00000010 <loop>:
  10:   e7fe        b.n 10 <loop>

00000012 <reset>:
  12:   4802        ldr r0, [pc, #8]    ; (1c <loop_counts>)

00000014 <loop_top>:
  14:   3801        subs    r0, #1
  16:   d1fd        bne.n   14 <loop_top>
  18:   e7fb        b.n 12 <reset>
  1a:   46c0        nop         ; (mov r8, r8)

0000001c <loop_counts>:
  1c:   00001234    andeq   r1, r0, r4, lsr r2

仅仅够一个指令集模拟器来运行该程序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define ROMMASK 0xFFFF
#define RAMMASK 0xFFF

unsigned short rom[ROMMASK+1];
unsigned short ram[RAMMASK+1];

unsigned int reg[16];
unsigned int pc;
unsigned int cpsr;
unsigned int inst;

int main ( void )
{
    unsigned int ra;
    unsigned int rb;
    unsigned int rc;
    unsigned int rx;

    //just putting something there, a real chip might have an MBIST, might not.
    memset(reg,0xBA,sizeof(reg));
    memset(ram,0xCA,sizeof(ram));
    memset(rom,0xFF,sizeof(rom));

    //in a real chip the rom/flash would contain the program and not
    //need to do anything to it, this sim needs to have the program
    //various ways to have done this...

                            //00000000 <_start>:
    rom[0x00>>1]=0x1000;    //   0: 20001000    andcs   r1, r0, r0
    rom[0x02>>1]=0x2000;
    rom[0x04>>1]=0x0013;    //   4: 00000013    andeq   r0, r0, r3, lsl r0
    rom[0x06>>1]=0x0000;
    rom[0x08>>1]=0x0011;    //   8: 00000011    andeq   r0, r0, r1, lsl r0
    rom[0x0A>>1]=0x0000;
    rom[0x0C>>1]=0x0011;    //   c: 00000011    andeq   r0, r0, r1, lsl r0
    rom[0x0E>>1]=0x0000;
                            //
                            //00000010 <loop>:
    rom[0x10>>1]=0xe7fe;    //  10: e7fe        b.n 10 <loop>
                            //
                            //00000012 <reset>:
    rom[0x12>>1]=0x4802;    //  12: 4802        ldr r0, [pc, #8]    ; (1c <loop_counts>)
                            //
                            //00000014 <loop_top>:
    rom[0x14>>1]=0x3801;     //  14:    3801        subs    r0, #1
    rom[0x16>>1]=0xd1fd;     //  16:    d1fd        bne.n   14 <loop_top>
    rom[0x18>>1]=0xe7fb;     //  18:    e7fb        b.n 12 <reset>
    rom[0x1A>>1]=0x46c0;     //  1a:    46c0        nop         ; (mov r8, r8)
                            //
                            //0000001c <loop_counts>:
    rom[0x1C>>1]=0x0004;     //  1c:    00001234    andeq   r1, r0, r4, lsr r2
    rom[0x1E>>1]=0x0000;


    //reset
    //THIS IS NOT SOFTWARE DRIVEN LOGIC, IT IS JUST LOGIC
    ra=rom[0x00>>1];
    rb=rom[0x02>>1];
    reg[14]=(rb<<16)|ra;
    ra=rom[0x04>>1];
    rb=rom[0x06>>1];
    rc=(rb<<16)|ra;
    if((rc&1)==0) return(1); //normally run a fault handler here
    pc=rc&0xFFFFFFFE;
    reg[15]=pc+2;
    cpsr=0x000000E0;

    //run
    //THIS PART BELOW IS SOFTWARE DRIVEN LOGIC
    //still you can see that each instruction requires some amount of
    //non-software driven logic.
    //while(1)
    for(rx=0;rx<20;rx++)
    {
        inst=rom[(pc>>1)&ROMMASK];
printf("0x%08X : 0x%04X\n",pc,inst);        
        reg[15]=pc+4;
        pc+=2;
        if((inst&0xF800)==0x4800)
        {
            //LDR
printf("LDR r%02u,[PC+0x%08X]",(inst>>8)&0x7,(inst&0xFF)<<2);
            ra=(inst>>0)&0xFF;
            rb=reg[15]&0xFFFFFFFC;
            ra=rb+(ra<<2);
printf(" {0x%08X}",ra);            
            rb=rom[((ra>>1)+0)&ROMMASK];
            rc=rom[((ra>>1)+1)&ROMMASK];
            ra=(inst>>8)&0x07;
            reg[ra]=(rc<<16)|rb;
printf(" {0x%08X}\n",reg[ra]);            
            continue;
        }
        if((inst&0xF800)==0x3800)
        {
            //SUB
            ra=(inst>>8)&0x07;
            rb=(inst>>0)&0xFF;
printf("SUBS r%u,%u ",ra,rb);
            rc=reg[ra];
            rc-=rb;
            reg[ra]=rc;
printf("{0x%08X}\n",rc);            
            //do flags
            if(rc==0) cpsr|=0x80000000; else cpsr&=(~0x80000000); //N flag
            //dont need other flags for this example
            continue;
        }
        if((inst&0xF000)==0xD000) //B conditional
        {
            if(((inst>>8)&0xF)==0x1) //NE
            {
                ra=(inst>>0)&0xFF;
                if(ra&0x80) ra|=0xFFFFFF00;
                rb=reg[15]+(ra<<1);
printf("BNE 0x%08X\n",rb);
                if((cpsr&0x80000000)==0)
                {
                    pc=rb;
                }
                continue;
            }
        }
        if((inst&0xF000)==0xE000) //B 
        {
            ra=(inst>>0)&0x7FF;
            if(ra&0x400) ra|=0xFFFFF800;
            rb=reg[15]+(ra<<1);
printf("B 0x%08X\n",rb);
            pc=rb;
            continue;
        }

        printf("UNDEFINED INSTRUCTION 0x%08X: 0x%04X\n",pc-2,inst);
        break;
    }
    return(0);
}

欢迎您讨厌我的编码风格,这是对这个问题的一种粗暴的对待。不,我不为ARM工作,所有这些都可以从公共(public)文档/信息中获取。我将循环缩短至4个计数,以查看它是否击中了外部循环
0x00000012 : 0x4802
LDR r00,[PC+0x00000008] {0x0000001C} {0x00000004}
0x00000014 : 0x3801
SUBS r0,1 {0x00000003}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000002}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000001}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000000}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000018 : 0xE7FB
B 0x00000012
0x00000012 : 0x4802
LDR r00,[PC+0x00000008] {0x0000001C} {0x00000004}
0x00000014 : 0x3801
SUBS r0,1 {0x00000003}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000002}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000001}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000000}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000018 : 0xE7FB
B 0x00000012

也许这会有所帮助,也许会使情况变得更糟。大多数逻辑不是由指令驱动的,每个指令都需要一定数量的逻辑,这些逻辑不计算诸如取指令之类的通用逻辑。

如果添加更多代码,此模拟器将破坏它,仅支持以下少数说明和此循环。

关于重置后的ARM PC值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51529896/

相关文章:

arm - 如何通过 USB 读取 swd 输出?

assembly - 如何在arm中编码立即值?

arm - 应使用哪种 ARM 处理器通过以太网传输数据?

c++ - armcc中.arm.extab条目的结构是什么?

c - 未定义对 _write 的引用,该引用在库中实现

使用 C 将 uint32_t 十六进制值转换为 ascii 十进制 LCD

c - 什么是 C 中的 vuint,为什么它在我的微 Controller 中?

c - C 中的基本中断操作

timer - STM32F1定时器共享用于PWM和中断

c++ - 如何交换STM32L475闪存中不同存储体的两个存储区域?