C代码中访问链接脚本中的符号

news2024/11/18 20:18:46

一、目的

在之前的《GNU LD脚本命令语言(一)》、《GNU LD脚本命令语言(二)》我们介绍了GNU链接脚本的知识点,基本上对链接脚本中的SECTION、REGION、以及加载地址与执行地址的关系等内容有了一定的了解。

本篇主要讲解链接脚本的符号如何在C代码中进行访问。

二、介绍

实际问题分析

在使用RT-Thread Studio进行STM32H750的bootloader代码阅读过程,大家肯定看到过此段代码


RT_WEAK void rt_hw_board_init()
{
    extern void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq);

    /* Heap initialization */
#if defined(RT_USING_HEAP)
    rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END);  //①
#endif

    hw_board_init(BSP_CLOCK_SOURCE, BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ);

    /* Set the shell console output device */
#if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE)
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

    /* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

}

①注意此行代码中的两个宏HEAP_BEGIN和HEAP_END

#define RAM_START              (0x24000000)
#define RAM_SIZE               (512)
#define RAM_END                (RAM_START + RAM_SIZE * 1024)

#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN      ((void *)&Image$$RW_IRAM1$$ZI$$Limit)
#elif __ICCARM__
#pragma section="CSTACK"
#define HEAP_BEGIN      (__segment_end("CSTACK"))
#else
extern int __bss_end;
#define HEAP_BEGIN      ((void *)&__bss_end)  //①
#endif

#define HEAP_END                       STM32_SRAM1_END  //②

①HEAP_BEGIN的值是变量__bss_end的地址,注意此处的__bss_end的类型声明是extern的。

②HEAP_END的值是STM32H750的SRAM的地址末尾,即0x24000000 + 512 * 1024,这个很容易理解,从地址映射表上可以查询的到。

我们搜索整个工程没有找到在代码中找到__bss_end的定义。但是我们在链接脚本文件以及最后的map文件中找到__bss_end的符号

链接脚本中的__bss_end符号定义了bss段的末尾地址

通过map文件分析我们可以看到bss段的末尾地址为0x2400205c,也就是说从这个地址开始我们将SRAM中剩余的内存作为系统堆使用。

那么c代码中的&__bss_end和链接脚本中的__bss_end有什么关系呢?


参考文档

Source Code Reference (LD) (sourceware.org)

在链接脚本中我们可以定义变量,在源代码中(C/C++、Fortran )也可以定义变量。

在链接脚本中定义的变量(或者叫符号)只有地址,没有值;

在源码中定义的变量既有地址又有值。

由于C++支持函数重载,所以编译后符号表中的函数名称会改变。

这边为了统一描述我们假设编译器编译后符号名称不变。

假如我们在链接脚本中定义了符号

_foo = 1000

那么就可以在源码中按照下面的代码引用这个符号

 extern int _foo;

注意源码中的符号我们只能对其取地址,因为这个变量只有地址没有值。

(void *)&_foo;

知识点

When a symbol is declared in a high level language such as C, two things happen. The first is that the compiler reserves enough space in the program’s memory to hold the value of the symbol. The second is that the compiler creates an entry in the program’s symbol table which holds the symbol’s address. ie the symbol table contains the address of the block of memory holding the symbol’s value

When a program references a symbol the compiler generates code that first accesses the symbol table to find the address of the symbol’s memory block and then code to read the value from that memory block. 

在高级语中(例如C)声明一个符号时,其内部其实做了两个事,第一编译器在系统内存中保留了足够的内存空间来存放这个符号的值;第二编译器在系统符号表中创建了一个新的条目(表项)用来存放符号的地址,也就是说通过符号名可以找到符号占用的内存的地址,通过此地址,可以知道符号里面对应的值。

举例说明

int foo = 1000;  //①
foo = 1;  //②

①定义了一个变量其初值为1000,也就是foo占用的内存(四字节)里面保存的是数字1000;

②将foo占用的内存里面的值修改为1;这边涉及到两个操作,第一通过foo在符号表中找到其代表的地址值,第二通过找到的地址值找到foo占用的内存空间,然后修改为1

Linker scripts symbol declarations, by contrast, create an entry in the symbol table but do not assign any memory to them. Thus they are an address without a value. So for example the linker script definition:

  foo = 1000;
creates an entry in the symbol table called ‘foo’ which holds the address of memory location 1000, but nothing special is stored at address 1000. This means that you cannot access the value of a linker script defined symbol - it has no value - all you can do is access the address of a linker script defined symbol.

如果在链接脚本中定义了一个符号,只会在符号表中添加一个新的条目,不会为这个符号分配任何内存,也就说这个符号只有地址没有值。

上图中符号A只有地址没有值

因为符号只有地址,所以我们只能通过&取这个符号的地址,而不能取值


假如我们在源码中想将.ROM段的数据拷贝到FLASH段

start_of_ROM   = .ROM;
end_of_ROM     = .ROM + sizeof (.ROM);
start_of_FLASH = .FLASH;
extern char start_of_ROM, end_of_ROM, start_of_FLASH; //①
memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM); //②

①②注意extern声明和&取地址符号。

如果符号是数组怎么办?数组的名称(符号)就是代表其地址,所以下面的代码片段与上面的功能一致。

extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];
memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);

那如果在汇编语言中应该怎么操作呢?

/* start address for the initialization values of the .data section. 
defined in linker script */
.word  _sidata
/* start address for the .data section. defined in linker script */  
.word  _sdata
/* end address for the .data section. defined in linker script */
.word  _edata
/* start address for the .bss section. defined in linker script */
.word  _sbss
/* end address for the .bss section. defined in linker script */
.word  _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */

/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called. 
 * @param  None
 * @retval : None
*/

    .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
Reset_Handler:  
  ldr   sp, =_estack      /* set stack pointer */

/* Copy the data segment initializers from flash to SRAM */  
  movs  r1, #0
  b  LoopCopyDataInit

CopyDataInit:
  ldr  r3, =_sidata
  ldr  r3, [r3, r1]
  str  r3, [r0, r1]
  adds  r1, r1, #4
    
LoopCopyDataInit:
  ldr  r0, =_sdata  
  ldr  r3, =_edata
  adds  r2, r0, r1
  cmp  r2, r3
  bcc  CopyDataInit
  ldr  r2, =_sbss
  b  LoopFillZerobss
/* Zero fill the bss segment. */  
FillZerobss:
  movs  r3, #0
  str  r3, [r2], #4
    
LoopFillZerobss:
  ldr  r3, = _ebss
  cmp  r2, r3
  bcc  FillZerobss

/* Call the clock system intitialization function.*/
  bl  SystemInit   
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/
  bl  entry
  bx  lr    
.size  Reset_Handler, .-Reset_Handler

注意观察_sidata、_sdata、_edata等等这些都是在链接脚本中定义的

至此,本篇的内容介绍完毕,点赞+收藏,永远不迷路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业4.0是如何优化垃圾处理行业的

如今,工业4.0正在影响着制造业和物流等行业,其发展潜力在未来还有望进一步扩大。一些全球领先的垃圾处理公司已经开始在水处理和废物回收等领域应用工业4.0。工业4.0的创新给这个领域带来了一些必要的改进。随着环境危机的加剧,垃圾处理行业面…

2022年最新数据库调查报告:超八成DBA月薪过万,你拖后腿了吗?

数据库管理员属于IT行业高薪职业的一种,近几年关于数据库管理员的薪资统计文章也层出不穷,那么当前,DBA们的薪资究竟到达了怎样的水平呢?墨天轮数据社区发布最新《2022年墨天轮数据库大调查报告》,数据显示超八成DBA月…

《MySQL学习》 全局锁和表锁

一.MySQL锁的分类 二.全局锁 全局锁对整个数据库加锁,可以执行如下命令,整个数据库都将处于只读状态。 Flush tables with read lock ;我们可以执行 unlock table进行解锁 unlock table ;读操作 非读操作(阻塞) 全局锁的典型使…

【并发编程】【2】进程与线程

并发编程 2.进程与线程 2.1 进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管…

开源大数据分析工具有几大内容?

在数据越来越重要的今天,数据管理的重要性不言而喻。引用专业的开源大数据分析工具可以为企业实现数字化办公,提升效率,提高数据管理品质和效率。我们今天就一起来了解下开源大数据分析工具的详细内容吧。 一、实现数据分析的重要性 在以前&a…

Java面试——Spring 事务

目录 1.什么是Spring 事务 2.Spring 事务的开启方式 3.Spring事务的实现方式/原理 4.事务传播机制 5.事务隔离级别 6.事务失效的原因 1.什么是Spring 事务 事务在逻辑上是一组操作,要么执行,要不都不执行。 如下: Begin; insert into…

【java】Spring Boot --spring boot项目整合xxl-job

文章目录1、源码下载地址2.文档地址3.源码结构4.初始化数据库脚本5.配置调度中心xxl-job-admin5.1 修改调度中心配置文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties5.2 启动调度中心5.3 访问调度中心管理界面6.创建执行器项目6.3 载入配置…

Framework——【MessageQueue】消息队列

定义 队列是 Apache RocketMQ 中消息存储和传输的实际容器,也是 Apache RocketMQ 消息的最小存储单元。 Apache RocketMQ 的所有主题都是由多个队列组成,以此实现队列数量的水平拆分和队列内部的流式存储。 队列的主要作用如下: 存储顺序性…

BUUCTF-练习场-WEB-第一部分(8道)

[极客大挑战 2019]EasySQL 1payload:1 or 11#是闭合前面的查询语句,or 11恒成立,可以使用or句子绕过判断,#用于注释,注释后面的内容不再执行,所以该sql命令会返回表内所有内容,其实就是实现一个…

JavaSE学习day7_01 面向对象

1. 类和对象 1.1 类和对象的理解 客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。即各个对象的总称,比如学生是一个类,但是学生有很多个,每一个称之为对象。 类 类的理解 类是对现实生活中一类具有共同属性和行为的事物的…

Apifox-接口调用、自动化测试工具

Apifox简介 Apifox 的定位是Postman Swagger Mock JMeter,具有API文档管理、API调试、API Mock、API 自动化测试等功能。可以通过一种工具解决之前使用多种工具的数据同步问题。高效、及时、准确! 安装 Apifox的安装非常方便,直接下载安…

ASEMI中低压MOS管ASE60N10参数,ASE60N10规格

编辑-Z ASEMI中低压MOS管ASE60N10参数: 型号:ASE60N10 漏极-源极电压(VDS):100V 栅源电压(VGS):20V 漏极电流(ID):60A 功耗(PD&…

从矩阵中提取对角线元素;将一维数组转换为对角线矩阵:np.diag()函数

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】从矩阵中提取对角线元素将一维数组转换为对角线矩阵np.diag()函数选择题下列说法错误的是?import numpy as npmyarray1 np.array([1,2,3])print("【显示】myarray1")print(myarray1…

Django框架之模型shell工具和查看MySQL数据库日志

shell工具和查看MySQL数据库日志 1 shell工具 Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。 通过如下命令进入shell python manage.py …

菜鸟在 windows 下 python 中安装 jupyter 踩坑要点 、被神化的 VsCode

我平时用不到 python ,更没用过 jupyter ,因此我的 python知识仅限于知道有 python 这么个编程语言,会写个 print("Hello World!!!") 而已,完全没听过 jupyter ,因为某些原因今天需要安装下 jupyter 看看&am…

记进组后第五次组会汇报

2023年2月14日 日记一、小组组会二、实验室组会1、汇报内容(1)参考文献(2)CQF机制a.研究现状b.相关思考(3)研究计划2、汇报反馈一、小组组会 上午十点整,小组组会开始,有两个同学我…

DAS, NAS, SAN谁才是你的偏爱

大家好,我是技福的小咖老师。 随着主机、磁盘、网络等技术的发展,数据存储的方式和架构,也在一直不停的改变,今天就来给大家介绍一下目前主流的存储架构。 存储的分类 根据服务器类型分为: ● 封闭系统的存储&#…

QGIS中进行批量坡度计算

QGIS中进行批量坡度计算1. 坡度计算中的Z因子(垂直单位与水平单位的比值)2. 坡度计算步骤1. 坡度计算中的Z因子(垂直单位与水平单位的比值) z 因子是一个转换因子,当输入表面的垂直坐标(或高程&#xff09…

对撞双指针(一) 盛水最多的容器

描述 给定一个数组height,长度为n,每个数代表坐标轴中的一个点的高度,height[i]是在第i点的高度,请问,从中选2个高度与x轴组成的容器最多能容纳多少水 1.你不能倾斜容器 2.当n小于2时,视为不能形成容器&…

Spring Security 源码解读:OAuth2 Authorization Server

样例代码请参考:spring-security-oauth2.0-server-sample Spring Authorization Server刚发展不久,还没有springboot版本,而Resource Server有,但是两个底层很多不兼容,会重复引入不同版本的jar包。 另外&#xff0c…