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…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
Xcode 16 集成 cocoapods 报错
基于 Xcode 16 新建工程项目,集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...
用 FFmpeg 实现 RTMP 推流直播
RTMP(Real-Time Messaging Protocol) 是直播行业中常用的传输协议。 一般来说,直播服务商会给你: ✅ 一个 RTMP 推流地址(你推视频上去) ✅ 一个 HLS 或 FLV 拉流地址(观众观看用)…...
JS设计模式(5): 发布订阅模式
解锁JavaScript发布订阅模式:让代码沟通更优雅 在JavaScript的世界里,我们常常会遇到这样的场景:多个模块之间需要相互通信,但是又不想让它们产生过于紧密的耦合。这时候,发布订阅模式就像一位优雅的信使,…...
基于开源AI大模型AI智能名片S2B2C商城小程序源码的中等平台型社交电商运营模式研究
摘要:本文聚焦中等平台型社交电商,探讨其与传统微商及大型社交电商平台的差异,尤其关注产品品类管理对代理运营的影响。通过引入开源AI大模型、AI智能名片与S2B2C商城小程序源码技术,构建智能化运营体系。研究结果表明,…...
【Linux】文件赋权(指定文件所有者、所属组)、挂载光驱(图文教程)
文章目录 文件赋权创建文件 testChmod查看文件的当前权限使用 chmod 命令修改权限验证权限关键命令总结答案汇总 光驱挂载确认文件是否存在打包压缩压缩验证创建 work 目录将压缩文件复制到 work 目录新建挂载点 /MNT/CDROM 并挂载光驱答案汇总 更多相关内容可查看 此篇用以解决…...
