使用GNU工具链进行嵌入式裸机开发

发布者:温柔之风最新更新时间:2024-08-01 来源: cnblogs关键字:嵌入式  裸机开发 手机看文章 扫描二维码
随时随地手机看文章

        .align

start:

        ldr   r0, =arr          @ r0 = &arr

        ldr   r1, =eoa          @ r1 = &eoa

 

        bl    sum               @ Invoke the sum subroutine

stop:   b stop

Listing 5. sum-sub.s - Subroutine Definition

        @ Args

        @ r0: Start address of array

        @ r1: End address of array

        @

        @ Result

        @ r3: Sum of Array

        .global sum

sum:    mov   r3, #0            @ r3 = 0

loop:   ldrb  r2, [r0], #1      @ r2 = *r0++    ; Get array element

        add   r3, r2, r3        @ r3 += r2      ; Calculate sum

        cmp   r0, r1            @ if (r0 != r1) ; Check if hit end-of-array

        bne   loop              @    goto loop  ; Loop

        mov   pc, lr            @ pc = lr       ; Return when done

关于.global指令的解释是由必要的。在C中,在函数外部声明的所有变量对其他文件都是可见的,直到明确说明为static。在汇编中,所有标签都是static的,也称本地(对文件而言),直到明确声明它们应该对其他文件可见,这时就需要使用.global指令来修饰。


文件被汇编后,并使用nm命令转储符号表。


$ arm-none-eabi-as -o main.o main.s

$ arm-none-eabi-as -o sum-sub.o sum-sub.s

$ arm-none-eabi-nm main.o

00000004 t arr

00000007 t eoa

00000008 t start

00000018 t stop

         U sum

$ arm-none-eabi-nm sum-sub.o

00000004 t loop

00000000 T sum

现在,关注第二列的字母,它指定了符号的类型。t表示这个符号在text段是定义了的。u表示这个符号未定义。大写字母表示符号是.global的。


很明显符号sum在sum-sub.o中定义了,不过在main.o中还未解析。当链接器被调用后,符号引用被解析,然后生成可执行文件。


6.2.重定位

重定位是改变标签已分配地址的过程。这还包括调整所有标签的引用地址以使对应上最新分配的地址。重定位主要基于以下两个原因:


段合并

段排布

要理解重新定位的过程,理解段的概念是必不可少的。


代码和数据有不同的run-time需求。例如代码可以放置在只读的存储介质中,数据则要放置在读-写存储介质中。如果代码和数据不混在一起,也许会更合适。为此,程序被分割为段。大多程序至少包含2个段,.text段放代码,.data段放数据。汇编器指令.text和.data用于在这两个段间来回切换。


可以把每个段想象成一个桶。当汇编器识别到一个段指令时,它会把紧跟指令的代码/数据放到对应的桶里面。因此,属于特定段的代码/数据的位置是紧挨着的。下面的图显示了汇编器如何将数据重新排列到段中。


Figure 3. Sections

现在我们已经了解了段,让我们看看执行重定位的主要原因。


6.2.1.段合并

当处理多源文件程序时,在每个文件中可能会出现同样名字的段(例如.text)。链接器负责将输入文件的段合并到一起放到输出文件对应的段中。默认情况下,拥有同样名字的段会被放置到一起,标签引用的地址也会被调整到对应的新的地址上。


通过查看目标文件和相应的可执行文件的符号表,可以看到段合并的效果。多源文件的sum of array程序可以用来阐明段合并。目标文件main.o、sum-sum.o和可执行文件sum.elf的符号表如下所示。


$ arm-none-eabi-nm main.o

00000004 t arr

00000007 t eoa

00000008 t start

00000018 t stop

         U sum

$ arm-none-eabi-nm sum-sub.o

00000004 t loop ❶

00000000 T sum

$ arm-none-eabi-ld -Ttext=0x0 -o sum.elf main.o sum-sub.o

$ arm-none-eabi-nm sum.elf

...

00000004 t arr

00000007 t eoa

00000008 t start

00000018 t stop

00000028 t loop ❷

00000024 T sum

❶ ❷ loop符号在sum-sub.o中地址是0x4,在sum.elf中地址是0x28。这是因为sum-sub.o的.text段恰好放置在main.o的.text之后。


6.2.2.段排布

当一个程序被汇编后,它的每个段都假定从0地址开始。因此,标签被分配的值是相对于段的起始处的。最后可执行文件生成时,段被放置到某个地址X上。并且所有对该部分中定义的标签的引用都被加上一个X的偏移,因此它们指向新的位置。


每个段在内存中的特定位置的排布,对段中每个标签引用的调整都是由链接器来完成的。


通过查看目标文件的符号表和相应的可执行文件,可以看到段排布的效果。单源文件的sum of array程序可以用来说明节的位置。为了更清楚,我们将把.text段放在地址0x100处。


$ arm-none-eabi-as -o sum.o sum.s

$ arm-none-eabi-nm -n sum.o

00000000 t entry ❶

00000004 t arr

00000007 t eoa

00000008 t start

00000014 t loop

00000024 t stop

$ arm-none-eabi-ld -Ttext=0x100 -o sum.elf sum.o ❷

$ arm-none-eabi-nm -n sum.elf

00000100 t entry ❸

00000104 t arr

00000107 t eoa

00000108 t start

00000114 t loop

00000124 t stop

...

❶ 在一个段内,标签的地址从0开始分配。


❷ 创建可执行文件时,指定链接器将.text段放在地址0x100处。


❸ .text段中标签的地址被从地址0x100处开始重新分配,所有标签引用都被调整反应了这一点。


段合并、段排布的过程如下图所示。


Figure 4. Section Merging and Placement

7.链接脚本文件

如前一节所述,段的合并和段的排布是由链接器完成的。编程人员可以通过一个链接脚本文件控制段如何合并以及它们在内存中的位置。下面是一个非常简单的链接脚本。


Listing 6. Basic linker script

SECTIONS { ❶

        . = 0x00000000; ❷

        .text : { ❸

                abc.o (.text);

                def.o (.text);

        } ❹

}

❶ SECTIONS命令是最重要的链接器命令,它指定了如何合并这些段以及将它们放置在什么位置。


❷ 在SECTIONS命令之后的语句块,.(dot)表示位置计数器。位置总是初始化为0x00000000。可以通过给它赋一个新的值来修改它。将开始处的位置计数器赋值为0x00000000是多余的。


❸ ❹ 链接脚本的这个部分指定了,输入文件abc.o、def.o的.text段将被放置到输出文件的.text段。


通过使用通配符*而不是单独指定文件名,可以进一步简化和通用化链接器脚本。


Listing 7. Wildcard in linker scripts

SECTIONS {

        . = 0x00000000;

        .text : { * (.text); }

}

如何程序既包含.text段也包含.data段,.data段的合并和位置可以像下面这样指定。


Listing 8. Multiple sections in linker scripts

SECTIONS {

         . = 0x00000000;

         .text : { * (.text); }

 

         . = 0x00000400;

         .data : { * (.data); }

}

此处.text段被放置到地址0x00000000处,.data被放置到地址0x00000400处。注意,如果位置计数器未分配不同的值,则.text和.data段会被放置到相邻的存储位置。


7.1.链接脚本示例

为了演示链接器脚本的使用,我们将使用[listing-8-multiple-sections-in-linker-scripts](Listing 8. Multiple sections in linker scripts)中所示的链接器脚本来控制程序的.text和.data段的排布。为此,我们将使用稍微修改过的sum of array程序。代码如下所示。


        .data

arr:    .byte 10, 20, 25        @ Read-only array of bytes

eoa:                            @ Address of end of array + 1

        .text

start:

        ldr   r0, =eoa          @ r0 = &eoa

        ldr   r1, =arr          @ r1 = &arr

        mov   r3, #0            @ r3 = 0

loop:   ldrb  r2, [r1], #1      @ r2 = *r1++

        add   r3, r2, r3        @ r3 += r2

        cmp   r1, r0            @ if (r1 != r2)

        bne   loop              @    goto loop

stop:   b stop

这里唯一的变化是数组现在位于.data部分。还要注意,不需要使用额外的分支指令跳过数据部分,因为链接器脚本将适当地放置.text段和.data段。因此,语句可以以任何方便的方式放置在程序中,而链接脚本将负责将这些段正确地放置在内存中。


当一个程序被链接时,链接脚本作为一个输入传给链接器,如下命令。


$ arm-none-eabi-as -o sum-data.o sum-data.s

$ arm-none-eabi-ld -T sum-data.lds -o sum-data.elf sum-data.o

选项-T sum-data.lds指定了sum-data.lds将作为链接脚本。转储符号表,将使您了解如何在内存中放置段。


$ arm-none-eabi-nm -n sum-data.elf

00000000 t start

0000000c t loop

0000001c t stop

00000400 d arr

00000403 d eoa

从符号表中可以明显看出·text段是从地址0x0开始放置的,.data段是从地址0x400开始放置的。


8.数据位于RAM中示例

现在我们知道了如何编写链接器脚本,我们将尝试编写一个程序,并将.data部分放在RAM中。


将add程序修改为从RAM加载两个值,将它们相加并将结果存储回RAM。两个值和结果存放在.data段。


Listing 9. Add Data in RAM

        .data

val1:   .4byte 10               @ First number

val2:   .4byte 30               @ Second number

result: .4byte 0                @ 4 byte space for result

        .text

        .align

start:

        ldr   r0, =val1         @ r0 = &val1

        ldr   r1, =val2         @ r1 = &val2

 

        ldr   r2, [r0]          @ r2 = *r0

        ldr   r3, [r1]          @ r3 = *r1

 

        add   r4, r2, r3        @ r4 = r2 + r3

 

        ldr   r0, =result       @ r0 = &result

        str   r4, [r0]          @ *r0 = r4

stop:   b stop

使用下面的链接脚本。


SECTIONS {

        . = 0x00000000;

        .text : { * (.text); }

 

        . = 0xA0000000;

        .data : { * (.data); }

}

elf文件的符号表转储如下所示。


$ arm-none-eabi-nm -n add-mem.elf

00000000 t start

0000001c t stop

a0000000 d val1

a0000001 d val2

a0000002 d result

链接脚本似乎已经解决了在RAM中放置.data段的问题。但是等等,解决方案还没有完成!


8.1.RAM是易失性的

RAM是易失性的存储介质,因此不可能在开机时直接在RAM中使数据。(要从非易失性的存储介质复制过来)


所有代码和数据都应该在开机前存储在Flash中。在启动时,启动代码应该将数据从Flash复制到RAM,然后继续执行程序。因此,程序的.data段有两个地址,Flash中的加载地址和RAM中的运行时地址。


Tip


在ld的说法中,加载地址称为LMA(Load Memory Address),run-time地址称为VMA(Virtual Memory Address)。


为了让程序正常运行,需要做下面2个修改。


链接器需要同时指定.data段的加载地址和运行地址。

一段用于将.data段从Flash(加载地址)拷贝至RAM(运行地址)的代码。

8.2.指定加载地址

run-time地址应该用于确定标签的地址。在前面的链接脚本中,我们为.data段指定了运行地址,但是加载地址没有显式指定,而是默认为运行时地址。对于前面的示例,这是可以的,因为程序是直接从Flash执行的。但是,如果要在执行期间将数据放在RAM中,那么加载地址应该与Flash对应,而运行时地址应该与RAM对应。


可以使用AT关键字指定与运行地址不同的加载地址。修改后的链接器脚本如下所示。


SECTIONS {

        . = 0x00000000;

        .text : { * (.text); }

        etext = .; ❶

 

        . = 0xA0000000;

        .data : AT (etext) { * (.data); } ❷

}

❶ 可以在SECTIONS命令中通过为符号赋值来动态创建符号。这里,etext被赋值为该位置的位置计数器的值。etext包含代码段之后Flash中的下一个空闲位置的地址。稍后将使用它来指定.data段在Flash中的位置。注意etext本身不会分配任何内存,它只是符号表中的一个条目。


❷ AT关键字指定.data段的加载地址。地址或符号(其值是有效地址)可以作为参数传递给AT。这里.data的加载地址被指定为Flash中代码段之后的位置。


8.3.拷贝.data至RAM

要拷贝.data至RAM,需要下面的信息。


data在Flash中的位置(flash_sdata)。

data在RAM中的位置(ram_sdata)。

.data段的大小(data_size)。

有了这些信息,可以使用以下代码片段将数据从Flash复制到RAM。


        ldr   r0, =flash_sdata

        ldr   r1, =ram_sdata

        ldr   r2, =data_size

copy:

        ldrb  r4, [r0], #1

        strb  r4, [r1], #1

        subs  r2, r2, #1

        bne   copy

可以稍微修改链接脚本以提供这些信息。


Listing 10. Linker Script with Section Copy Symbols

SECTIONS {

        . = 0x00000000;

        .text : {

              * (.text);

[1] [2] [3] [4]
关键字:嵌入式  裸机开发 引用地址:使用GNU工具链进行嵌入式裸机开发

上一篇:s3c2440调试nandflash裸机程序遇到的问题
下一篇:MMU 和 MPU的区别

推荐阅读最新更新时间:2026-02-25 12:44

ARM裸机开发:按键输入实验
一、硬件平台: 正点原子I.MX6U阿尔法开发板 二、原理图分析 按键输入是配置GPIO作为输入,检测按键引脚电平,采用扫描的方式读取按键按下的信息,IMX6UL的按键引脚如下: 可以看到按键引脚接到 GPIO1_IO18 口,按键的原理就是默认接一个上拉电阻,按键按下接地,可以有效控制 IO 电平 三、配置代码 按键工程我们基于上一节工程进行开发,添加 BSP_KEY 工程文件 编写 bsp_key.h 文件 #ifndef __BSP_KEY_H #define __BSP_KEY_H #include fsl_iomuxc.h #include MCIMX6Y2.h #include bsp
[单片机]
ARM<font color='red'>裸机</font><font color='red'>开发</font>:按键输入实验
三星6410裸机程序开发2:建立eclipse裸机程序工程
网上关于S3C6410裸机程序开发都是基于RealView RVDS。也有一些是基于eclipse的,但都没有详细介绍在eclipse中如何建立S3C6410裸机程序工程。 尽管友善之臂提供的6410裸机程序示例使用了eclipse工程,然程序的编译却还是基于makefile的。那怎样建立6410的eclipse裸机程序工程呢?本文就此进行详细介绍。有了这个工程,可以简化裸机程序的开发过程,把精力专注于功能实现。 设置eclipse字体 因为友善之臂提供的裸机程序示例使用的是UTF-8编码,为正常使用这些源码,把eclipse的字体编码设为UTF-8格式。Windows— Preferences中,选择General— W
[单片机]
OK6410A 开发板 (八) 25 linux-5.11 OK6410A 进程角度 裸机和进程的区别
裸机 代码 : 没有调度函数 有进程概念的代码 : 有调度函数 添加调度要添加什么 添加调度要添加什么 1. pick next task 调度算法 选择 哪个进程 为 下一个进程 2. switch 寄存器的保存和恢复 其他细节 1. 在什么时候调度/即调度时机 调度的技术设想 考虑到 要选择 下一个进程, 考虑 用 单链表即可 考虑到 要保存 寄存器, 考虑用一个 结构体(不同的体系架构有不同的寄存器个数与bit) 保存即可 考虑到换出,当前cpu就无法访问,但是换入,还要能恢复,就需要全部变量 考虑到 每个 任务 要 一套(区分进程/选择进程/保存进程上下文),将 int类型变量/单链表/结
[单片机]
【番外篇】mini2440裸机开发——分散加载文件scatter
一、分散加载文件的原理和MDK上的配置 ARM的连接器提供了一种分散加载机制,在连接时可以根据分散加载文件(.scf文件)中指定的存储器分配方案,将可执行镜像文件分成指定的分区并定位于指定的存储器物理地址。这样,当嵌入式系统在复位或重新上电时,在对CPU相应寄存器进行初始化后,首先执行ROM存储器的Bootloader代码,根据连接时的存储器分配方案,将相应代码和数据由加载地址拷贝到运行地址,这样,定位在RAM存储器的代码和数据就在RAM存储器中运行,而不再从ROM存储器中取数据或取指令,从而大大提高了CPU的运行速率和效率。 在Keil中Linker选项中,可以设置R/O Base来设置RO区域的加载和执行地址,R/W
[单片机]
【番外篇】mini2440<font color='red'>裸机</font><font color='red'>开发</font>——分散加载文件scatter
[初级教程]arm-linux裸机开发之-bootstrap.bin的实现
1.前言 我们知道,在Linux平台下编写的程序要想在arm平台上运行,我们就得使用交叉的编译器,我们用arm-linux-none-gnueabi-gcc 或者arm-linux-gcc也罢,这些编译器编译产生的程序能够直接运行在arm的平台上,那我们的bootstrap.bin自己来实现,都需要具备哪些文件呢?众所周知,在引导程序中当今做大的最大的莫过于U-Boot这个组织,今天,我们就模仿U-Boot来实现一个简易的引导程序,我们称之为Bootstrap.bin。实现该bin文件我们模仿U-Boot的工程模版,写出几个实现文件: start.S 我们知道汇编指令的代码在机器上执行的速度比较快,那些短小精悍的程序往往运行在
[单片机]
[初级教程]arm-linux<font color='red'>裸机</font><font color='red'>开发</font>之-bootstrap.bin的实现
[初级教程]搭建arm-linux裸机开发的环境
1.前言 在上一遍的文章中,我介绍了如何设置芯片的启动模式,根据三星的官方主推的IROM模式,介绍了如何从IROM模式启动,并从Nand中加载我们的引导程序,有了一些前面的知识铺垫后,这一篇文章,我将进一步深入,教你如何实现你的引导程序,并搭建一个调试引导程序的实验环境。这里我介绍两种方式,这两种方式各有前提条件,具体如下: 2.开发环境的搭建 开发环境其实大同小异,总结一下用到的几个: windows主机 vmware虚拟机 在vmware虚拟机中安装ubuntu桌面操作系统或者redhat或者debain或者centOS均可,看你自己习惯 调试工具(H-JTAG或者J-link) H-JTAG和J-link的调试搭建环
[单片机]
七、2440裸机开发 触摸屏操作
七、lcd触摸屏控制 触摸屏就是当接触了屏幕上的图形按钮时,屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置,可用以取代机械式的按钮面板。2440连接的是电阻式触摸屏,利用压力感应进行控制,电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在他们之间有许多细小的(小于1/1000英寸)的透明隔离点把两层导电层隔开绝缘。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。
[单片机]
七、2440<font color='red'>裸机</font><font color='red'>开发</font> 触摸屏操作
Exynos4412裸机开发 —— A/D转换器
一、Exynos4412 A/D转换器概述 1、简述 10位或12位CMOS再循环式模拟数字转换器,它具有10通道输入,并可将模拟量转换至10位或12位二进制数。5Mhz A/D 转换时钟时,最大1Msps的转换速度。A/D转换具备片上采样保持功能,同时也支持待机工作模式。 2、特性 ADC接口包括如下特性。 1)10bit/12bit输出位可选。 2)微分误差 1.0LSB。 3)积分误差 2.0LSB。 4)最大转换速率5Msps. 5) 功耗少,电压输入1.8V。 6)电压输入范围 0~1.8V。 7)支持偏上样本保持功能。 8)通用转换模式。 3、模块图 4412A/D转换器的控制器接口框图如下: 二、Ex
[单片机]
Exynos4412<font color='red'>裸机</font><font color='red'>开发</font> —— A/D转换器
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved