C代码中访问链接脚本中的符号
一、目的
在之前的《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); //①
#endifhw_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_INITrt_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 valueWhen 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, #0b LoopCopyDataInitCopyDataInit:ldr r3, =_sidataldr r3, [r3, r1]str r3, [r0, r1]adds r1, r1, #4LoopCopyDataInit:ldr r0, =_sdata ldr r3, =_edataadds r2, r0, r1cmp r2, r3bcc CopyDataInitldr r2, =_sbssb LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:movs r3, #0str r3, [r2], #4LoopFillZerobss:ldr r3, = _ebsscmp r2, r3bcc FillZerobss/* Call the clock system intitialization function.*/bl SystemInit
/* Call static constructors */
/* bl __libc_init_array */
/* Call the application's entry point.*/bl entrybx lr
.size Reset_Handler, .-Reset_Handler注意观察_sidata、_sdata、_edata等等这些都是在链接脚本中定义的

至此,本篇的内容介绍完毕,点赞+收藏,永远不迷路。
相关文章:
C代码中访问链接脚本中的符号
一、目的在之前的《GNU LD脚本命令语言(一)》、《GNU LD脚本命令语言(二)》我们介绍了GNU链接脚本的知识点,基本上对链接脚本中的SECTION、REGION、以及加载地址与执行地址的关系等内容有了一定的了解。本篇主要讲解链…...
MySQL 8:MySQL索引
索引就是通过一定的算法建立数据模型,用于快速查找某一列中具有特定值的行。如果没有索引,MySQL 必须从第一条记录开始读取整个表,直到找到相关的表。表越大,查询数据所花费的时间就越多。如果表中查询的列有索引,MySQ…...
JVM详解
一,JVM 1,JVM区域划分 类装载器,运行时数据区,字节码执行引擎 2,JVM内存模型(运行时数据区) 由本地方法栈,虚拟机栈,堆,方法区,和程序计数器组成。…...
MySQL数据库调优————索引数据结构
B-TREE B-TREE数据结构 B-TREE特性 根节点的子结点个数2 < X < m,m是树的阶 假设m 3,则根节点可有2-3个孩子 中间节点的子节点个数m/2 < y < m 假设m 3,中间节点至少有2个孩子,最多3个孩子 每个中间节点包含n个关…...
visual studio 改变界面语言
在使用visual studio 2019 时,开始是英文界面,后面变成了中文界面。但是看视频教学时有的是英文界面,我就想回到英文界面,所以有切换界面语言的需要。其实操作很简单:工具-> 选项 打开界面在界面里选择环境…...
2023.2.16每日一题——1250. 检查「好数组」
每日一题题目描述解题核心解法一:数论题目描述 题目链接:1250. 检查「好数组」 给你一个正整数数组 nums,你需要从中任选一些子集,然后将子集中每一个数乘以一个 任意整数,并求出他们的和。 假如该和结果为 1&#x…...
亿级高并发电商项目-- 实战篇 --万达商城项目 八(安装FastDFS、安装Nginx、文件服务模块、文件上传功能、商品功能与秒杀商品等功能)
专栏:高并发---分布式项目 👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者 📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信支…...
Viper捐款7000万韩元,合计人民币是多少钱?
Viper捐款7000万韩元,合计人民币是多少钱? #2023LCK春季赛##英雄联盟# #Viper捐款7000万韩元# Viper向大田东区捐款 7000 万,成为大田荣誉协会 105 号会员。Viper选手从 2019 年开始一直向大田东区捐款,但是他不希望这件事被公开…...
前端vue实现系统拦截跳转外链并进入跳转询问界面
跳转询问界面如下图所示: 给自己挖坑的实现方式,最终解决方案请看最底下 思路:正常情况下我们有2种方式跳转外链 第一种非a标签,我们手动添加事件进行跳转 <div class"dingdan public-padding p-item" click&quo…...
【Linux】Shell(Bash)单引号、双引号、不加引号和反引号用法和区别详解
简要总结 不加引号:不会将含有空格的字符串视为一个整体输出, 如果内容中有变量等,会先把变量解析出结果,然后在输出最终内容来,如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号ÿ…...
本人使用的idea插件
文章目录🚏 本人使用的idea插件🚬 pojo to Json🚬 GsonFormatPlus🚬 EasyYapi🚬 Chinese (Simplified) Language Pack / 中文语言包🚬 MyBatis Log Free🚬 MyBatisPlusX🚬 Statistic…...
站在行业C位,谷医堂打开健康管理服务新思路
对于农村及贫困地区老百姓来说,由于交通因素和家庭经济条件制约,看病难致身体调理情况一直不太乐观,这也导致心理压力很大。然而,随着近年中医药产业崛起与快速发展,这种局面很快就会得到改观,以湖南谷医堂…...
ABO溶血症概率
[简介]ABO溶血是由于母亲和胎儿ABO血型不合引起的新生儿溶血,概率不是很大,一般出现在准妈妈是O血,准爸爸是非O血,这次容易发生血型不合,但新生儿ABO溶血概率不高,大多数症状相对较轻。ABO溶血的概率是什么…...
【算法数据结构体系篇class03】:数组、链表、栈、队列、递归时间复杂度、哈希表、有序表问题
一、反转链表package class03;import java.util.ArrayList; import java.util.List;/*** 链表反转*/ public class ReverseLinkedList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {p…...
【新2023】华为OD机试 - 最多提取子串数目(Python)
最多提取子串数目 题目 给定由 [a-z] 26 个英文小写字母组成的字符串 A 和 B,其中 A 中可能存在重复字母,B 中不会存在重复字母 现从字符串 A 中按规则挑选一些字母,可以组成字符串 B。 挑选规则如下: 1) 同一个位置的字母只能被挑选一次 2) 被挑选字母的相对先后顺序不…...
嵌入式C语言设计模式 --- 代理模式
1 - 什么是代理模式? 代理模式(Proxy Pattern),是指当客户端无法访问某个对象或者访问某个对象存在困难的时候,可以通过一个代理对象来进行间接访问。 举一个生活中的例子,比如,我们在买火车票或者飞机票的时候,有时候不会直接在12306或者航空公司官网上面购买,而是…...
前端性能优化的整理笔记
🚴 前言大厂面试题分享 面试题库后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库🏄利用碎片化的时间,系统的整理,性能优化的知识点。🎯 前端性能优化…...
springboot+mybatis连接数据库实现增删改查功能
springbootmybatis连接数据库实现增删改查功能创建表创建项目实体类DAO接口写sql的XML文件Service层Controller启动类结果目录结构参考博客创建表 create table user(id int ,name varchar(30),pwd varchar(40) )insert into user values(2,hxf,789101),(3,hlm,789102),(4,hzh…...
疑似45亿地址信息泄露事件跟进后续
开放隐私计算 收录于合集#数据安全13个开放隐私计算开放隐私计算OpenMPC是国内第一个且影响力最大的隐私计算开放社区。社区秉承开放共享的精神,专注于隐私计算行业的研究与布道。社区致力于隐私计算技术的传播,愿成为中国 “隐私计算最后一公里的服务区…...
Hadoop集群配置
一、系统文件配置集群部署规划NameNode和SecondaryNameNode不要安装在同一台服务器ResourceManager也很消耗内存,不要和NameNode、SecondaryNameNode放在同一台机器上。这里装了四台机器,ant151,ant152,ant153,ant154。ant151ant152ant153ant154NameNode…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
【若依】框架项目部署笔记
参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...
