裸机编程 Raspberry Pi 3.
Bare metal programming Raspberry Pi 3.
我正在学习一些裸机编程教程。在阅读 C 代码执行时,我开始知道我们需要设置 C 执行环境,如初始化堆栈归零 bss 等。
在某些情况下,您必须在 ram 中复制数据,并且还需要为此提供启动代码。 Link 的教程,它说在 RAM 中复制数据。
现在有两个疑惑。
如果我们需要在 RAM 中复制数据,那么我们为什么不复制代码,即文本段。如果我们不复制文本段是否意味着在 Raspberry pi 3(Arm 嵌入式处理器)的情况下代码是从 SD 卡本身执行的。
当我们像下面这样指定链接描述文件时,是否建议将这些部分复制到 RAM 中,或者这些部分将映射到 RAM 地址中?
对不起,我真的很困惑。
MEMORY
{
ram : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ram
.bss : { *(.bss*) } > ram
}
感谢任何帮助。
vectors.s
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
notmain.c
unsigned int x;
unsigned int y=0x12345678;
void notmain ( void )
{
x=y+7;
}
内存映射
MEMORY
{
bob : ORIGIN = 0x80000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > ted
}
建设
arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf -O binary kernel.img
你可以 add/remove 选项,并将其命名为正确的 kernelX.img (如果你打算进入 64 位,那么使用 aarch64-whatever-gcc 而不是 arm-whatever-gcc ...
看反汇编
Disassembly of section .text:
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000000 bl 800c <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <notmain>:
800c: e59f3010 ldr r3, [pc, #16] ; 8024 <notmain+0x18>
8010: e5933000 ldr r3, [r3]
8014: e59f200c ldr r2, [pc, #12] ; 8028 <notmain+0x1c>
8018: e2833007 add r3, r3, #7
801c: e5823000 str r3, [r2]
8020: e12fff1e bx lr
8024: 00008030 andeq r8, r0, r0, lsr r0
8028: 0000802c andeq r8, r0, r12, lsr #32
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
00008030 <y>:
8030: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
并将其与 kernelX.img 文件进行比较
hexdump -C kernel.img
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 30 80 00 00 2c 80 00 00 00 00 00 00 |../.0...,.......|
00000030 78 56 34 12 |xV4.|
00000034
请注意,因为我在 linker 脚本中将 .data 放在 .bss 之后,所以它在图像中按该顺序放置了它们。 .text 的最后一个字和 .data
的 0x12345678 之后有四个字节的零
如果在 linker 脚本中交换 .bss 和 .data 的位置
0000802c <y>:
802c: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
Disassembly of section .bss:
00008030 <x>:
8030: 00000000 andeq r0, r0, r0
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 2c 80 00 00 30 80 00 00 78 56 34 12 |../.,...0...xV4.|
00000030
糟糕,没有免费赠品。现在 .bss 没有归零,你需要在你的 bootstrap 中将它归零(如果你有一个 .bss 区域并且作为一种编程风格,你假设这些项目在你第一次使用它们时为零)。
好的,那么你如何找到 .bss 在哪里?好吧,这就是本教程和无数其他教程向您展示的内容。
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
linker_stuff:
.word hello_world
.word world_hello
MEMORY
{
bob : ORIGIN = 0x80000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.data : { *(.data*) } > ted
hello_world = .;
.bss : { *(.bss*) } > ted
world_hello = .;
}
构建和反汇编
Disassembly of section .text:
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000002 bl 8014 <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <linker_stuff>:
800c: 00008038 andeq r8, r0, r8, lsr r0
8010: 0000803c andeq r8, r0, r12, lsr r0
00008014 <notmain>:
8014: e59f3010 ldr r3, [pc, #16] ; 802c <notmain+0x18>
8018: e5933000 ldr r3, [r3]
801c: e59f200c ldr r2, [pc, #12] ; 8030 <notmain+0x1c>
8020: e2833007 add r3, r3, #7
8024: e5823000 str r3, [r2]
8028: e12fff1e bx lr
802c: 00008034 andeq r8, r0, r4, lsr r0
8030: 00008038 andeq r8, r0, r8, lsr r0
Disassembly of section .data:
00008034 <y>:
8034: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
Disassembly of section .bss:
00008038 <x>:
8038: 00000000 andeq r0, r0, r0
因此,深入挖掘特定于工具链的内容,我们现在可以知道 .bss 的开始和结束,或者可以在 linker 脚本中使用数学来获取大小和长度。从中您可以编写一个小循环来清零该内存(当然,在汇编语言中,先有鸡还是先有蛋,在跳转到程序的 C 入口点之前的 bootstrap)。
现在说出于某种原因你想要 .data 在其他地址 0x10000000
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > bob
}
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000000 bl 800c <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <notmain>:
800c: e59f3010 ldr r3, [pc, #16] ; 8024 <notmain+0x18>
8010: e5933000 ldr r3, [r3]
8014: e59f200c ldr r2, [pc, #12] ; 8028 <notmain+0x1c>
8018: e2833007 add r3, r3, #7
801c: e5823000 str r3, [r2]
8020: e12fff1e bx lr
8024: 10000000 andne r0, r0, r0
8028: 0000802c andeq r8, r0, r12, lsr #32
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
10000000 <y>:
10000000: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
那么 kernel.img 或 -O 二进制格式是什么?它只是一个从最低地址(在本例中为 0x8000)开始并填充或填充到最高地址(在本例中为 0x10000003)的内存映像,因此它是一个 0x10000004-0x8000 字节文件。
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 00 00 00 10 2c 80 00 00 00 00 00 00 |../.....,.......|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0fff8000 78 56 34 12 |xV4.|
0fff8004
对于这个程序来说,这是对磁盘的巨大浪费 space,他们把它填满了。现在,如果出于某种原因你想做这样的事情,各种原因(通常不适用于 pi 上的裸机),你可以改为这样做:
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > bob AT > ted
}
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 00 00 00 10 2c 80 00 00 00 00 00 00 |../.....,.......|
00000030 78 56 34 12 |xV4.|
00000034
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
10000000 <y>:
10000000: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
它所做的是编译代码并 linked 用于 0x10000000 处的 .data 但您随身携带和加载的二进制文件将 .data 数据捆绑在一起,这是bootstrap 将该数据复制到其正确的着陆点 0x10000000 并且您必须再次使用特定于工具链的 linker scripty stuff
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
linker_stuff:
.word data_start
.word data_end
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
data_start = .;
.data : { *(.data*) } > bob AT > ted
data_end = .;
}
0000800c <linker_stuff>:
800c: 00008038 andeq r8, r0, r8, lsr r0
8010: 10000004 andne r0, r0, r4
很明显,这不太奏效,所以你必须做更多 linker scripy 的东西来弄清楚。
没有充分的理由为 raspberry pi 需要任何这些,如果您有 .bss 而没有任何 .data and/or 最好将 .bss 放在最后,如果您有很多,那么您可以利用工具链意外地补零并为您解决 .bss 问题,或者如果二进制文件太大,那么您可以在上面看到如何找到 .bss 偏移量和大小然后添加只需几行代码即可将其归零(无论哪种方式最终都会花费加载时间,但不会花费 SD 卡 space)。
当你使用非易失性被视为只读闪存的微控制器时,如果你选择使用需要 .data 的样式进行编程,那么你肯定需要学习这些东西 and/or .bss 并且您假设这些项目已实现,那么您必须对 link 执行工具链特定工作,然后将零 and/or 从非易失性闪存复制到 read/write ram,然后再分支到第一个或者您的应用程序的唯一 C 入口点。
我敢肯定有人会提出不将 pi 裸机二进制文件打包得很好和整洁的理由,总有例外......但现在你不需要担心这些例外,把 .bss首先,然后是 .data,并始终确保您有一个 .data 项目,即使它是您从未使用过的东西。
我正在学习一些裸机编程教程。在阅读 C 代码执行时,我开始知道我们需要设置 C 执行环境,如初始化堆栈归零 bss 等。
在某些情况下,您必须在 ram 中复制数据,并且还需要为此提供启动代码。 Link 的教程,它说在 RAM 中复制数据。 现在有两个疑惑。
如果我们需要在 RAM 中复制数据,那么我们为什么不复制代码,即文本段。如果我们不复制文本段是否意味着在 Raspberry pi 3(Arm 嵌入式处理器)的情况下代码是从 SD 卡本身执行的。
当我们像下面这样指定链接描述文件时,是否建议将这些部分复制到 RAM 中,或者这些部分将映射到 RAM 地址中? 对不起,我真的很困惑。
MEMORY
{
ram : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ram
.bss : { *(.bss*) } > ram
}
感谢任何帮助。
vectors.s
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
notmain.c
unsigned int x;
unsigned int y=0x12345678;
void notmain ( void )
{
x=y+7;
}
内存映射
MEMORY
{
bob : ORIGIN = 0x80000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > ted
}
建设
arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf -O binary kernel.img
你可以 add/remove 选项,并将其命名为正确的 kernelX.img (如果你打算进入 64 位,那么使用 aarch64-whatever-gcc 而不是 arm-whatever-gcc ...
看反汇编
Disassembly of section .text:
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000000 bl 800c <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <notmain>:
800c: e59f3010 ldr r3, [pc, #16] ; 8024 <notmain+0x18>
8010: e5933000 ldr r3, [r3]
8014: e59f200c ldr r2, [pc, #12] ; 8028 <notmain+0x1c>
8018: e2833007 add r3, r3, #7
801c: e5823000 str r3, [r2]
8020: e12fff1e bx lr
8024: 00008030 andeq r8, r0, r0, lsr r0
8028: 0000802c andeq r8, r0, r12, lsr #32
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
00008030 <y>:
8030: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
并将其与 kernelX.img 文件进行比较
hexdump -C kernel.img
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 30 80 00 00 2c 80 00 00 00 00 00 00 |../.0...,.......|
00000030 78 56 34 12 |xV4.|
00000034
请注意,因为我在 linker 脚本中将 .data 放在 .bss 之后,所以它在图像中按该顺序放置了它们。 .text 的最后一个字和 .data
的 0x12345678 之后有四个字节的零如果在 linker 脚本中交换 .bss 和 .data 的位置
0000802c <y>:
802c: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
Disassembly of section .bss:
00008030 <x>:
8030: 00000000 andeq r0, r0, r0
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 2c 80 00 00 30 80 00 00 78 56 34 12 |../.,...0...xV4.|
00000030
糟糕,没有免费赠品。现在 .bss 没有归零,你需要在你的 bootstrap 中将它归零(如果你有一个 .bss 区域并且作为一种编程风格,你假设这些项目在你第一次使用它们时为零)。
好的,那么你如何找到 .bss 在哪里?好吧,这就是本教程和无数其他教程向您展示的内容。
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
linker_stuff:
.word hello_world
.word world_hello
MEMORY
{
bob : ORIGIN = 0x80000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.data : { *(.data*) } > ted
hello_world = .;
.bss : { *(.bss*) } > ted
world_hello = .;
}
构建和反汇编
Disassembly of section .text:
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000002 bl 8014 <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <linker_stuff>:
800c: 00008038 andeq r8, r0, r8, lsr r0
8010: 0000803c andeq r8, r0, r12, lsr r0
00008014 <notmain>:
8014: e59f3010 ldr r3, [pc, #16] ; 802c <notmain+0x18>
8018: e5933000 ldr r3, [r3]
801c: e59f200c ldr r2, [pc, #12] ; 8030 <notmain+0x1c>
8020: e2833007 add r3, r3, #7
8024: e5823000 str r3, [r2]
8028: e12fff1e bx lr
802c: 00008034 andeq r8, r0, r4, lsr r0
8030: 00008038 andeq r8, r0, r8, lsr r0
Disassembly of section .data:
00008034 <y>:
8034: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
Disassembly of section .bss:
00008038 <x>:
8038: 00000000 andeq r0, r0, r0
因此,深入挖掘特定于工具链的内容,我们现在可以知道 .bss 的开始和结束,或者可以在 linker 脚本中使用数学来获取大小和长度。从中您可以编写一个小循环来清零该内存(当然,在汇编语言中,先有鸡还是先有蛋,在跳转到程序的 C 入口点之前的 bootstrap)。
现在说出于某种原因你想要 .data 在其他地址 0x10000000
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > bob
}
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000000 bl 800c <notmain>
8008: eafffffe b 8008 <_start+0x8>
0000800c <notmain>:
800c: e59f3010 ldr r3, [pc, #16] ; 8024 <notmain+0x18>
8010: e5933000 ldr r3, [r3]
8014: e59f200c ldr r2, [pc, #12] ; 8028 <notmain+0x1c>
8018: e2833007 add r3, r3, #7
801c: e5823000 str r3, [r2]
8020: e12fff1e bx lr
8024: 10000000 andne r0, r0, r0
8028: 0000802c andeq r8, r0, r12, lsr #32
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
10000000 <y>:
10000000: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
那么 kernel.img 或 -O 二进制格式是什么?它只是一个从最低地址(在本例中为 0x8000)开始并填充或填充到最高地址(在本例中为 0x10000003)的内存映像,因此它是一个 0x10000004-0x8000 字节文件。
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 00 00 00 10 2c 80 00 00 00 00 00 00 |../.....,.......|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0fff8000 78 56 34 12 |xV4.|
0fff8004
对于这个程序来说,这是对磁盘的巨大浪费 space,他们把它填满了。现在,如果出于某种原因你想做这样的事情,各种原因(通常不适用于 pi 上的裸机),你可以改为这样做:
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > bob AT > ted
}
00000000 02 d9 a0 e3 00 00 00 eb fe ff ff ea 10 30 9f e5 |.............0..|
00000010 00 30 93 e5 0c 20 9f e5 07 30 83 e2 00 30 82 e5 |.0... ...0...0..|
00000020 1e ff 2f e1 00 00 00 10 2c 80 00 00 00 00 00 00 |../.....,.......|
00000030 78 56 34 12 |xV4.|
00000034
Disassembly of section .bss:
0000802c <x>:
802c: 00000000 andeq r0, r0, r0
Disassembly of section .data:
10000000 <y>:
10000000: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
它所做的是编译代码并 linked 用于 0x10000000 处的 .data 但您随身携带和加载的二进制文件将 .data 数据捆绑在一起,这是bootstrap 将该数据复制到其正确的着陆点 0x10000000 并且您必须再次使用特定于工具链的 linker scripty stuff
.globl _start
_start:
mov sp,#0x8000
bl notmain
b .
linker_stuff:
.word data_start
.word data_end
MEMORY
{
bob : ORIGIN = 0x10000000, LENGTH = 0x1000
ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ted
.rodata : { *(.rodata*) } > ted
.bss : { *(.bss*) } > ted
data_start = .;
.data : { *(.data*) } > bob AT > ted
data_end = .;
}
0000800c <linker_stuff>:
800c: 00008038 andeq r8, r0, r8, lsr r0
8010: 10000004 andne r0, r0, r4
很明显,这不太奏效,所以你必须做更多 linker scripy 的东西来弄清楚。
没有充分的理由为 raspberry pi 需要任何这些,如果您有 .bss 而没有任何 .data and/or 最好将 .bss 放在最后,如果您有很多,那么您可以利用工具链意外地补零并为您解决 .bss 问题,或者如果二进制文件太大,那么您可以在上面看到如何找到 .bss 偏移量和大小然后添加只需几行代码即可将其归零(无论哪种方式最终都会花费加载时间,但不会花费 SD 卡 space)。
当你使用非易失性被视为只读闪存的微控制器时,如果你选择使用需要 .data 的样式进行编程,那么你肯定需要学习这些东西 and/or .bss 并且您假设这些项目已实现,那么您必须对 link 执行工具链特定工作,然后将零 and/or 从非易失性闪存复制到 read/write ram,然后再分支到第一个或者您的应用程序的唯一 C 入口点。
我敢肯定有人会提出不将 pi 裸机二进制文件打包得很好和整洁的理由,总有例外......但现在你不需要担心这些例外,把 .bss首先,然后是 .data,并始终确保您有一个 .data 项目,即使它是您从未使用过的东西。