1)将编译后的二进制文件写入prom / flash yes。USB,串行,i2c,jtag等取决于该设备所支持的设备,这与了解启动过程无关。
2)对于微控制器通常情况并非如此,主要用例是将指令存储在rom / flash中,将数据存储在ram中。无论采用哪种架构。对于非微控制器,您的PC,笔记本电脑,服务器,程序将从非易失性(磁盘)复制到ram,然后从那里运行。有些微控制器也允许您使用ram,即使那些声称违反哈佛定义的人也可以使用ram。哈佛没有什么可以阻止您将ram映射到指令端的,您只需要有一种机制,在加电后就可以在那里获取指令(这违反了定义,但是哈佛系统必须这样做才能使其他有用)比作为微控制器)。
3)。
每个cpu按照设计的确定性方式“启动”。最常见的方式是向量表,其中上电后要执行的第一条指令的地址位于复位向量中,硬件会读取该地址,然后使用该地址开始运行。另一种通用方法是让处理器开始执行,而无需在某个众所周知的地址使用向量表。有时,芯片会带有“绑带”,您可以在释放复位之前将某些引脚连接到高电平或低电平,该逻辑用于引导不同的方式。您必须将cpu本身(处理器核心)与系统的其余部分分开。了解cpu的工作方式,然后了解芯片/系统设计人员在cpu的外部具有设置地址解码器,以便cpus地址空间的某些部分与闪存进行通信,有些带有ram,有些带有外围设备(uart,i2c,spi,gpio等)。如果愿意,可以采用相同的cpu核心,并以不同的方式包装它。这是您购买手臂或arm嘴产品时得到的。arm和mips制作了cpu内核,人们购买和包装自己的东西时会用到这些芯片,由于各种原因,他们无法使这些东西在品牌之间相互兼容。这就是为什么当涉及核心以外的问题时,很少有人会问一个通用的问题。
微控制器试图成为一个芯片上的系统,因此其非易失性存储器(闪存/ ROM),易失性(SRAM)和CPU都与同一个外围设备混合在同一芯片上。但是芯片是内部设计的,因此闪存被映射到与该CPU的启动特性匹配的CPU的地址空间中。例如,如果cpu在地址0xFFFC处有一个复位向量,则需要有flash / rom来响应我们可以通过1)编程的地址,并且在地址空间中要有足够的flash / rom来进行有用的编程。芯片设计人员可以选择从0xF000开始具有0x1000字节的闪存,以满足这些要求。也许他们在较低地址或0x0000处放置了一些ram,外围设备则在中间。
cpu的另一种体系结构可能从地址零开始执行,因此他们需要做相反的事情,放置闪存,以使其响应零附近的地址范围。例如说0x0000到0x0FFF。然后在其他地方放一些公羊。
芯片设计师知道CPU的启动方式,并在其中放置了非易失性存储(闪存/ ROM)。然后由软件人员编写引导代码以匹配该CPU的众所周知的行为。您必须将重置向量地址放置在重置向量中,并将引导代码放置在重置向量中定义的地址上。工具链可以在这里极大地帮助您。有时,尤其是指向和点击ide或其他沙盒,它们可能为您完成大部分工作,而您要做的就是用高级语言(C)调用api。
但是,无论如何,加载到flash / rom中的程序必须匹配cpu的硬接线启动行为。在程序main()的C部分之前,以及如果使用main作为入口点,则必须完成一些操作。AC程序员假定,当声明一个具有初始值的变量时,他们希望该变量实际起作用。好吧,除了const变量外,其他变量都在ram中,但是如果您有一个带有初始值的变量,则初始值必须在非易失性ram中。因此,这是.data段,C引导程序需要将.data内容从闪存复制到ram(通常由工具链为您确定位置)。您声明的没有初始值的全局变量在程序启动之前被假定为零,尽管您实际上不应假定这样做,而且值得庆幸的是,一些编译器开始警告未初始化的变量。这是.bss段,C引导程序将ram中的零置零,内容零不必存储在非易失性存储器中,而是起始地址和多少。同样,工具链在这里可以为您提供极大的帮助。最后,最基本的要求是您需要设置堆栈指针,因为C程序希望能够具有局部变量并调用其他函数。然后也许完成其他一些特定于芯片的工作,或者让其余的特定于芯片的工作发生在C中。不一定要存储在非易失性存储器中,而是起始地址及其大小。同样,工具链在这里可以为您提供极大的帮助。最后,最基本的要求是您需要设置堆栈指针,因为C程序希望能够具有局部变量并调用其他函数。然后也许完成其他一些特定于芯片的工作,或者让其余的特定于芯片的工作发生在C中。不一定要存储在非易失性存储器中,而是起始地址及其大小。同样,工具链在这里可以为您提供极大的帮助。最后,最基本的要求是您需要设置堆栈指针,因为C程序希望能够具有局部变量并调用其他函数。然后也许完成其他一些特定于芯片的工作,或者让其余的特定于芯片的工作发生在C中。
来自arm的cortex-m系列内核将为您完成某些工作,堆栈指针位于向量表中,有一个重置向量指向重置后要运行的代码,因此您无需执行其他任何操作要生成向量表(通常无论如何通常使用asm),都可以使用纯C语言而不使用asm。现在,您无需复制.data或将.bss清零,因此,如果您想尝试在基于cortex-m的东西上不使用asm,则必须自己做。更大的功能不是复位向量,而是中断向量,其中硬件遵循推荐的C调用约定并为您保留寄存器,并为该向量使用正确的返回值,因此您不必在每个处理程序周围包装正确的asm(或针对目标使用特定于工具链的指令,以使工具链为您包装它)。
例如,特定于芯片的东西可能是微控制器,通常在基于电池的系统中使用,因此功耗低,因此有些器件会在大多数外设关闭的情况下退出复位状态,因此您必须打开每个子系统才能使用它们。Uart,gpios等。通常使用低时钟频率,直接来自晶体或内部振荡器。而且您的系统设计可能表明您需要更快的时钟,因此您将其初始化。您的时钟对于Flash或ram来说可能太快了,因此您可能需要在更改时钟之前更改等待状态。可能需要设置uart,usb或其他接口。那么您的应用程序就可以完成它的工作。
计算机台式机,笔记本电脑,服务器和微控制器的启动/工作方式没有什么不同。除了它们不是大多数都在一个芯片上。BIOS程序通常位于与CPU分离的单独Flash / ROM芯片上。尽管最近x86 cpus将越来越多的过去支持芯片的东西(同一个pcie控制器等)拉到同一个封装中,但是您仍然拥有大部分的ram和rom芯片,但是它仍然是一个系统,并且仍然可以正常工作在高水平上相同。cpu引导过程是众所周知的,电路板设计人员将flash / rom放置在cpu引导的地址空间中。该程序(x86电脑上BIOS的一部分)完成了上述所有操作,启动了各种外围设备,初始化了dram,枚举了pcie总线,依此类推。用户通常可以根据bios设置或我们以前称为cmos设置的方式进行配置,因为当时使用的是技术。没关系,您可以更改用户设置,以告诉BIOS引导代码如何更改其功能。
不同的人会使用不同的术语。芯片启动,这是第一个运行的代码。有时称为引导程序。一个带有“装载程序”一词的引导程序通常意味着,如果您不做任何干扰的引导程序,它将引导您从常规引导进入更大的应用程序或操作系统。但是加载器部分意味着您可以中断引导过程,然后加载其他测试程序。例如,如果您曾经在嵌入式linux系统上使用过uboot,则可以按一个键并停止正常启动,然后可以将测试内核下载到ram中并启动它,而不是从闪存中启动,或者可以下载自己的程序,或者您可以下载新内核,然后让引导程序将其写入闪存,以便下次引导时运行新程序。
至于CPU本身,就是核心处理器,它不知道外设的闪存中的RAM。没有引导程序,操作系统,应用程序的概念。只是将指令序列馈送到要执行的cpu中。这些是软件术语,用于区分不同的编程任务。彼此之间的软件概念。
某些微控制器在芯片的单独闪存或单独的闪存区域中具有芯片供应商提供的单独的引导加载程序,您可能无法对其进行修改。在这种情况下,通常会有一个针脚或一组针脚(我称它们为皮带),如果您在释放复位之前将它们拉高或拉低,则会告诉逻辑和/或引导加载程序该做什么,例如,一个皮带组合可能告诉芯片运行该引导程序,然后在uart上等待将数据编程到闪存中。以其他方式设置束带,则程序不会启动芯片供应商的引导加载程序,从而允许对芯片进行现场编程或从程序崩溃中恢复。有时,纯逻辑允许您对闪存进行编程。如今这很普遍
大多数微控制器具有比ram更多的flash的原因是,主要用例是直接从flash运行程序,并且仅具有足够的ram来覆盖堆栈和变量。尽管在某些情况下,您可以从ram运行程序,但必须对其进行编译,然后将其存储在flash中,然后在调用之前进行复制。
编辑
闪存
.cpu cortex-m0
.thumb
.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang: b .
notmain.c
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
return(0);
}
flash.ld
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.rodata : { *(.rodata*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.bss*) } > ted AT > bob
}
因此,这是一个cortex-m0的示例,在这个示例中,cortex-ms的工作原理都相同。对于此示例,特定的芯片在臂地址空间中的地址为0x00000000的应用程序闪存,在0x20000000的ram的应用程序。
cortex-m引导方式是地址0x0000处的32位字,是初始化堆栈指针的地址。对于这个示例,我不需要太多堆栈,因此0x20001000就足够了,显然在该地址下方必须有ram(机械臂压入的方式,是先减去然后压入,所以如果您设置0x20001000,则堆栈中的第一项位于地址0x2000FFFC您不必使用0x2000FFFC)。地址0x0004的32位字是复位处理程序的地址,基本上是复位后运行的第一个代码。然后,还有更多特定于该皮质内核和芯片的中断和事件处理程序,可能多达128或256,如果您不使用它们,则无需为它们设置表,我专门介绍了一些目的。
在此示例中,我不需要处理.data或.bss,因为通过查看代码,我已经知道这些段中没有任何内容。如果有的话,我会处理,一会儿。
这样就完成了堆栈的设置,检查,.data处理,检查,.bss,检查,因此C引导程序工作已经完成,可以跳转到C的入口函数。因为某些编译器在看到该函数时会添加额外的垃圾main(),在通往main的路上,我没有使用确切的名称,在这里我使用notmain()作为我的C入口点。因此,重置处理程序将调用notmain(),然后如果/当notmain()返回时,它将挂起,这只是一个无限循环,可能命名不正确。
我坚信精通这些工具,很多人没有,但是您会发现,每个裸机开发人员都会做自己的事情,这是因为它们几乎完全自由,而不像您制作应用程序或网页那样受限制。他们再次做自己的事。我更喜欢拥有自己的引导程序代码和链接脚本。其他人则依靠工具链,或者在供应商沙盒中发挥作用,其中大部分工作是由其他人完成的(如果某件事中断了,您将处于一个充满伤害的世界中,而裸机则经常且以戏剧性的方式破坏)。
因此,我使用gnu工具进行组装,编译和链接:
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
那么,引导程序如何知道东西在哪里。因为编译器完成了工作。在第一种情况下,汇编器为flash.s生成了代码,这样做就知道标签在哪里(标签只是地址,就像函数名或变量名一样,等等),因此我不必计数字节并填写向量手动使用表格,我使用了标签名称,而汇编程序为我完成了该操作。现在,您问,如果复位地址为0x14,汇编器为何将0x15放在向量表中。好吧,这是一个cortex-m,它可以引导并且仅在拇指模式下运行。如果使用ARM模式,则当分支到Thumb模式时,如果分支到一个地址,则需要设置lsbit;如果ARM模式然后复位,则需要设置lsbit。因此,您始终需要设置该位。我知道这些工具,可以通过在标签前放置.thumb_func来实现,如果该标签在矢量表中被使用,或者用于分支到其他对象。工具链知道设置lsbit。因此此处为0x14 | 1 = 0x15。对于挂起也是如此。现在,反汇编程序不显示对notmain()的调用的0x1D,但不用担心工具正确地构建了指令。
现在,在notmain中的代码中,不再使用那些局部变量,它们是无效代码。编译器甚至通过说y已设置但未使用来评论该事实。
注意地址空间,这些东西都从地址0x0000开始,然后从那里开始,所以向量表被正确放置了,.text或程序空间也被正确放置了,我如何在notmain.c的代码前面得到flash.s。知道这些工具后,一个常见的错误就是无法正确使用它,然后崩溃并重燃。IMO,您必须拆卸一下以确保在第一次启动之前就将物品放置在正确的位置,一旦将物品放在正确的位置,则不必每次都检查一下。仅用于新项目或是否挂起。
现在让某些人感到惊讶的是,没有任何理由期望任何两个编译器从相同的输入产生相同的输出。甚至是具有不同设置的同一编译器。使用clang,通过llvm编译器,我可以得到有优化和无优化这两个输出
llvm / clang优化
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: 2000 movs r0, #0
1e: 4770 bx lr
没有优化
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f802 bl 1c <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <notmain>:
1c: b082 sub sp, #8
1e: 2001 movs r0, #1
20: 9001 str r0, [sp, #4]
22: 2002 movs r0, #2
24: 9000 str r0, [sp, #0]
26: 2000 movs r0, #0
28: b002 add sp, #8
2a: 4770 bx lr
所以这是一个谎言,编译器确实优化了加法运算,但是它确实在堆栈上为变量分配了两个项目,因为这些是局部变量,它们位于ram中,但是在堆栈上的地址不是固定的,因此可以看到全局变量变化。但是编译器意识到它可以在编译时计算y,并且没有理由在运行时计算它,因此它只是在为x分配的堆栈空间中放置了1,为y分配的堆栈空间中放置了2。编译器使用内部表“分配”此空间,我声明堆栈加0表示变量y,堆栈声明加4表示变量x。只要编译器实现的代码符合C标准或C程序员的要求,编译器就可以执行所需的任何操作。没有任何理由使编译器在函数持续时间内必须将x保留在堆栈+ 4处,
如果我在汇编器中添加函数虚拟
.thumb_func
.globl dummy
dummy:
bx lr
然后叫它
void dummy ( unsigned int );
int notmain ( void )
{
unsigned int x=1;
unsigned int y;
y = x + 1;
dummy(y);
return(0);
}
输出变化
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000015 andeq r0, r0, r5, lsl r0
8: 0000001b andeq r0, r0, fp, lsl r0
c: 0000001b andeq r0, r0, fp, lsl r0
10: 0000001b andeq r0, r0, fp, lsl r0
00000014 <reset>:
14: f000 f804 bl 20 <notmain>
18: e7ff b.n 1a <hang>
0000001a <hang>:
1a: e7fe b.n 1a <hang>
0000001c <dummy>:
1c: 4770 bx lr
...
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
现在我们有了嵌套函数,notmain函数需要保留其返回地址,以便它可以掩盖嵌套调用的返回地址。这是因为手臂使用寄存器来返回,如果它像x86或其他一些东西一样使用了堆栈……它仍然会使用堆栈,但是方式有所不同。现在您问为什么它推动了r4?好吧,不久前的调用约定发生了变化,以使堆栈在64位(两个字)的边界上对齐,而不是在32位(一个字)的边界上对齐。因此,他们需要推入一些东西来保持堆栈对齐,因此编译器出于某种原因任意选择了r4,无论原因如何。弹出到r4将是一个错误,尽管按照此目标的调用约定,我们不会在函数调用上破坏r4,而是可以通过r3破坏r0。r0是返回值。看起来它正在进行尾部优化,
但是我们看到x和y数学被优化为传递给虚拟函数的硬编码值2(虚拟对象专门编码在一个单独的文件中,在本例中为asm,因此编译器不会完全优化该函数的调用,如果我有一个虚函数仅在notmain.c中以C返回,则优化器将删除x,y和虚函数调用,因为它们都是无效的/无用的代码)。
还要注意,因为flash.s代码变得更大了,所以不是,并且工具链已经为我们修补了所有地址,因此我们不必手动进行。
未优化的clang供参考
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: b082 sub sp, #8
26: 2001 movs r0, #1
28: 9001 str r0, [sp, #4]
2a: 2002 movs r0, #2
2c: 9000 str r0, [sp, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: b002 add sp, #8
36: bd80 pop {r7, pc}
优化的lang
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 2002 movs r0, #2
26: f7ff fff9 bl 1c <dummy>
2a: 2000 movs r0, #0
2c: bd80 pop {r7, pc}
该编译器作者选择使用r7作为虚拟变量来对齐堆栈,即使它在堆栈框架中没有任何内容,它也正在使用r7创建框架指针。基本上本来可以优化指令。但是它使用了pop命令,没有返回三条指令,我敢打赌我可以让gcc使用正确的命令行选项(指定处理器)来执行此操作。
这应该大部分可以回答您的其余问题
void dummy ( unsigned int );
unsigned int x=1;
unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
我现在有全局变量。因此,如果未进行优化,它们就会进入.data或.bss。
在我们看最终输出之前,先看一下itermediate对象
00000000 <notmain>:
0: b510 push {r4, lr}
2: 4b05 ldr r3, [pc, #20] ; (18 <notmain+0x18>)
4: 6818 ldr r0, [r3, #0]
6: 4b05 ldr r3, [pc, #20] ; (1c <notmain+0x1c>)
8: 3001 adds r0, #1
a: 6018 str r0, [r3, #0]
c: f7ff fffe bl 0 <dummy>
10: 2000 movs r0, #0
12: bc10 pop {r4}
14: bc02 pop {r1}
16: 4708 bx r1
...
Disassembly of section .data:
00000000 <x>:
0: 00000001 andeq r0, r0, r1
现在,这里缺少信息,但是它给出了一个正在发生的情况的链接器,链接器是一个接收对象并将它们与提供的信息(在本例中为flash.ld)链接在一起的信息,该信息告诉它.text和的位置。数据等等。编译器不知道这样的事情,它只能专注于所提供的代码,它的任何外部都必须留有空隙供链接器填充连接。它必须留下任何将这些东西链接在一起的数据,因此这里的所有内容的地址都是零,仅仅是因为编译器和该反汇编程序不知道。链接器用来放置事物的其他信息未在此处显示。此处的代码足够独立于位置,因此链接程序可以完成其工作。
然后,我们至少看到链接输出的反汇编
00000020 <notmain>:
20: b510 push {r4, lr}
22: 4b05 ldr r3, [pc, #20] ; (38 <notmain+0x18>)
24: 6818 ldr r0, [r3, #0]
26: 4b05 ldr r3, [pc, #20] ; (3c <notmain+0x1c>)
28: 3001 adds r0, #1
2a: 6018 str r0, [r3, #0]
2c: f7ff fff6 bl 1c <dummy>
30: 2000 movs r0, #0
32: bc10 pop {r4}
34: bc02 pop {r1}
36: 4708 bx r1
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <y>:
20000000: 00000000 andeq r0, r0, r0
Disassembly of section .data:
20000004 <x>:
20000004: 00000001 andeq r0, r0, r1
编译器基本上要求在ram中提供两个32位变量。.bss中有一个,因为我没有初始化它,所以假定它被初始化为零。另一个是.data,因为我确实在声明时对其进行了初始化。
现在,由于这些是全局变量,因此假定其他函数可以修改它们。编译器不对何时可以调用notmain做出任何假设,因此它无法根据其所看到的y = x + 1数学进行优化,因此它必须执行该运行时。它必须从ram读取两个变量,然后将它们相加并保存。
现在,显然此代码将无法正常工作。为什么?因为这里所示的引导程序在调用notmain之前没有准备ram,所以在芯片唤醒时0x20000000和0x20000004中的任何垃圾将用于y和x。
这里不会显示。您可以阅读我对.data和.bss甚至更漫长的漫谈,以及为什么我在裸机代码中不再需要它们,但是如果您觉得自己必须并且想要掌握这些工具,而不是希望别人做对了。 。
https://github.com/dwelch67/raspberrypi/tree/master/bssdata
链接描述文件和引导程序在某种程度上是特定于编译器的,因此您所了解的有关一个编译器的一个版本的所有信息都可能被下一版本或其他某个编译器所困扰,这又是我不花大量精力进行.data和.bss准备的另一个原因只是为了这个懒惰:
unsigned int x=1;
我宁愿这样做
unsigned int x;
...
x = 1;
然后让编译器为我保存在.text中。有时它会以这种方式保存闪光灯,有时会燃烧更多。从工具链版本或一个编译器进行编程和移植无疑是最容易的。更可靠,更不易出错。是的,不符合C标准。
现在,如果我们使这些静态全局变量呢?
void dummy ( unsigned int );
static unsigned int x=1;
static unsigned int y;
int notmain ( void )
{
y = x + 1;
dummy(y);
return(0);
}
好
00000020 <notmain>:
20: b510 push {r4, lr}
22: 2002 movs r0, #2
24: f7ff fffa bl 1c <dummy>
28: 2000 movs r0, #0
2a: bc10 pop {r4}
2c: bc02 pop {r1}
2e: 4708 bx r1
显然,这些变量不能被其他代码修改,因此编译器现在可以像以前一样在编译时优化死代码。
未优化
00000020 <notmain>:
20: b580 push {r7, lr}
22: af00 add r7, sp, #0
24: 4804 ldr r0, [pc, #16] ; (38 <notmain+0x18>)
26: 6800 ldr r0, [r0, #0]
28: 1c40 adds r0, r0, #1
2a: 4904 ldr r1, [pc, #16] ; (3c <notmain+0x1c>)
2c: 6008 str r0, [r1, #0]
2e: f7ff fff5 bl 1c <dummy>
32: 2000 movs r0, #0
34: bd80 pop {r7, pc}
36: 46c0 nop ; (mov r8, r8)
38: 20000004 andcs r0, r0, r4
3c: 20000000 andcs r0, r0, r0
这个使用本地堆栈的编译器,现在将ram用于全局变量,由于我没有正确处理.data或.bss,编写的代码已损坏。
还有我们在反汇编中看不到的最后一件事。
:1000000000100020150000001B0000001B00000075
:100010001B00000000F004F8FFE7FEE77047000057
:1000200080B500AF04480068401C04490860FFF731
:10003000F5FF002080BDC046040000200000002025
:08004000E0FFFF7F010000005A
:0400480078563412A0
:00000001FF
我将x更改为使用0x12345678进行预初始化。我的链接程序脚本(这是针对gnu ld的)在bob上有这个问题。告诉链接器我希望最终位置在ted地址空间中,但将其存储在ted地址空间中的二进制文件中,然后有人会为您移动它。我们可以看到发生了这种情况。这是英特尔十六进制格式。我们可以看到0x12345678
:0400480078563412A0
在二进制文件的闪存地址空间中。
readelf也显示了这一点
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x010040 0x00000040 0x00000040 0x00008 0x00008 R 0x4
LOAD 0x010000 0x00000000 0x00000000 0x00048 0x00048 R E 0x10000
LOAD 0x020004 0x20000004 0x00000048 0x00004 0x00004 RW 0x10000
LOAD 0x030000 0x20000000 0x20000000 0x00000 0x00004 RW 0x10000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
LOAD行,其中虚拟地址为0x20000004,物理地址为0x48