GCC __attribute__ 和 link 脚本控制 section 基地址

本文介绍了如何使用GCC的__attribute__属性来控制数据区的基地址,并通过link脚本进一步调整section的基地址。通过具体代码示例和编译过程展示,实现了变量在不同section中的定位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GCC __attribute__ 和 link 脚本控制 section 基地址

网上看的一篇文章,感谢作者,另外加上自己的一点注释。
... ... ... ... .... .... .....

利用 GCC 的 __attribute__ 属性的 section 选项来控制数据区的基地址。
以下例子,主要涉及到两个知识点,一个是 GNU C 扩展中的 attribute section 属性,关于这个知识点的相关信息可以参考: http://www.groad.net/bbs/read.php?tid=1035
另外一个知识点是 ld 连接器所用到的 link 脚本相关知识。

测试代码
#include <stdio.h>
int localmemory0 __attribute__(( section( "LOCALmem"))) = 0;
int localmemory1 __attribute__(( section( "LOCALmem"))) = 0;
int globalmemory __attribute__(( section( "GLOBALmem"))) = 0;
int main ( int argc , char * argv [])
{
localmemory0 = 0x456;
localmemory1 = 0x123;
globalmemory = localmemory0 + localmemory1;
}

在上面的代码中,定义了两个非传统的 section : LOCALmem 和 GLOBALmem 。
程序里要求变量 localmemory0 和 localmemory1 存放在 section LOCALmem 中,而 globalmemory 存放在 section GLOBALmem 中。

下面编译程序
beyes@linux-beyes:~/C/GNU_C_EXT> gcc -c -o elf_section.o elf_section.c

或者
beyes@linux-beyes:~/C/GNU_C_EXT> gcc -c -o elf_section2.o -fdata-sections elf_section.c

-fdata-sections 选项的目的是让编译器为每一个单独申明的数据 section 实际分配一个 section,而不是占用 .bss ,在某些系统上需要显式的使用这一参数。

使用 objdump 命令查看生成的 elf_section.o 文件
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -S elf_section.o

elf_section.o: file format elf32-i386


Disassembly of section .text:

00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
11: c7 05 00 00 00 00 56 movl $0x456,0x0
18: 04 00 00
1b: c7 05 00 00 00 00 23 movl $0x123,0x0
22: 01 00 00
25: a1 00 00 00 00 mov 0x0,%eax
2a: 8b 15 00 00 00 00 mov 0x0,%edx
30: 01 d0 add %edx,%eax
32: a3 00 00 00 00 mov %eax,0x0
37: 83 c4 04 add $0x4,%esp
3a: 59 pop %ecx
3b: 5d pop %ebp
3c: 8d 61 fc lea -0x4(%ecx),%esp
3f: c3 ret

上面的 objdump 使用了 -S 选项,这是查看目标文件反汇编代码的一个选项;对此,也可以使用 -d 选项,并建议使用 -d 选项。
上面加蓝色高亮的两条 movl 指令分别将数据存放到 localmemory0 和 localmemroy1 ;加粉红色高亮的 mov 指令将两个变量相加的结构存放到 globalmemory 中。
由于是目标文件,变量符号对应的地址没有经过解析和分配,所以指令变量中对应的地址都是 0x0 。

下面,使用一个 link 脚本来控制连接器 ld 输出 section 的己地址(像linux的内核源码树中的 arch/arm/vmlinux-armv.lds.in 文件也是这样的脚本文件<这里是ARM平台,编译内核后的文件)。
文件内容如下:
SECTIONS
{
.text : {

*(.text)
}

LOCALmem 0x1f0000 : {

*(LOCALmem)
}

GLOBALmem 0xff0000 : {

*(GLOBALmem)
}

}

用下面的命令观察生成的 elf 文件内容:
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -S elf_section.elf

elf_section.elf: file format elf32-i386


Disassembly of section .text:

00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: c7 05 00 00 1f 00 56 movl $0x456,0x1f0000
15: 04 00 00
18: c7 05 04 00 1f 00 23 movl $0x123,0x1f0004
1f: 01 00 00
22: 8b 15 00 00 1f 00 mov 0x1f0000,%edx
28: a1 04 00 1f 00 mov 0x1f0004,%eax
2d: 8d 04 02 lea (%edx,%eax,1),%eax
30: a3 00 00 ff 00 mov %eax,0xff0000
35: b8 00 00 00 00 mov $0x0,%eax
3a: 59 pop %ecx
3b: 5d pop %ebp
3c: 8d 61 fc lea -0x4(%ecx),%esp
3f: c3 ret

说明
ld 命令的选项 -T ,等同于选项 -c ,这是告诉 ld 从 -c 后面的文件(commandfile) 中读取连接命令。这些命令彻底的覆盖 ld 的缺省连接格式 (不是添加);commandfile 必须详尽的描述目标格式的所有细节。
在两个加了蓝色高亮的语句中,可以看到,0x456 和 0x123 两数已经分别搬往 0x1f0000 与 0x1f0004 两个地址。

使用 objdump -s 来查看一下生成的 elf 文件:
beyes@linux-beyes:~/C/GNU_C_EXT> objdump -s elf_section.elf

elf_section.elf: file format elf32-i386

Contents of section .text:
0000 8d4c2404 83e4f0ff 71fc5589 e551c705 .L$.....q.U..Q..
0010 00001f00 56040000 c7050400 1f002301 ....V.........#.
0020 00008b15 00001f00 a104001f 008d0402 ................
0030 a30000ff 00b80000 0000595d 8d61fcc3 ..........Y].a..
Contents of section LOCALmem:
1f0000 00000000 00000000 ........
Contents of section GLOBALmem:
ff0000 00000000 ....
Contents of section .comment:
0000 00474343 3a202853 55534520 4c696e75 .GCC: (SUSE Linu
0010 78292034 2e332e32 205b6763 632d345f x) 4.3.2 [gcc-4_
0020 332d6272 616e6368 20726576 6973696f 3-branch revisio
0030 6e203134 31323931 5d00 n 141291].
Contents of section .comment.SUSE.OPTs:
0000 6f737077 6700 ospwg.

上面命令中, -s 选项表示显示任何一个 section 的所有内容。在默认情况下,所有非空 section 都要显示。

由上可见,section LOCALmem 从 0x1f0000 开始,而 section GLOBALmem 从 0xff0000 开始,程序正文段 .text 从 0x0 地址开始。
<think>我们正在讨论Keil C51环境下如何为code类型的数组指定绝对地址。根据引用[2]引用[3]中的示例,我们可以使用两种不同的方法:一种是使用`__attribute__((at(address)))`(在ARM编译器中),另一种是使用C51特有的`__at__`关键字。但请注意,C51ARM编译器在语法上有所不同。 在C51中,我们使用`__at__`关键字来指定绝对地址。对于code类型的数组(即存放在程序存储器中的常量数组),我们可以这样定义: ```c const unsigned char code MyArray[10] __at__ 0x1000 = {1,2,3,4,5,6,7,8,9,10}; ``` 但是,请注意以下要点: 1. **关键字位置**:`__at__`关键字放在变量名之后,初始化列表之前。 2. **存储类型**:使用`code`关键字指定数组位于程序存储器中。 3. **地址**:0x1000是一个示例地址,必须是目标硬件中有效的程序存储器地址。 然而,根据引用[1]中的内容,我们得知在C51中不能使用`__at__`来定位函数(包括main函数),但可以定位变量(包括数组)。对于函数的定位,需要采用其他方法(如链接器设置)。 另外,引用[2]引用[3]展示了在ARM编译器(如Keil MDK-ARM)中使用`__attribute__((at(address)))`的方法。但我们的问题是关于C51的,所以应使用C51的语法。 **重要注意事项**: - 在C51中,`__at__`不能用于初始化,但这里我们是在定义常量数组(使用`const``code`),并且允许在定义时初始化,因为这是常量数组的初始化,且位于code空间。 - 确保指定地址在程序存储器中,并且不会与其他代码或常量冲突。 **示例代码**: ```c // 将常量数组定位在CODE空间的0x1000地址 const unsigned char code MyArray[10] __at__ 0x1000 = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}; ``` **验证**: 编译后,查看生成的.M51文件(或.MAP文件),在符号表中查找`MyArray`,确认其地址是否为0x1000。 **替代方法(使用链接器控制)**: 如果不使用`__at__`,也可以通过链接器指令将数组定位到特定地址。例如,在项目选项中指定一个额外的代码段,并在链接器配置文件中指定该段的地址。但这种方法较为复杂,且需要修改链接器设置。 **总结**: 在Keil C51中,为code类型数组指定绝对地址,使用`__at__`关键字是一种直接有效的方法。但要注意地址的合法性,以及避免地址冲突。 [^1]: keil下代码的定位1 [^2]: KEIL将一个变量、数组定义到指定Flash地址 [^3]: [学习笔记] KEIL 如何把data,code放到指定地址 §§相关问题§§ 1. 在Keil C51中,如何将函数定位到绝对地址? 2. 使用`__at__`定位变量时如何避免链接器地址冲突? 3. Keil C51中,`code``const code`有何区别?</think>### Keil C51 中为 code 类型数组指定绝对地址的方法 在 Keil C51 环境中,为 `code` 类型数组(程序存储器中的常量数组)指定绝对地址有两种主要方法: #### 方法 1:使用 `__at__` 关键字(C51 特有语法) ```c const uint8_t code MyArray[10] __at__ (0x1000) = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A }; ``` **关键点**: 1. 必须包含 `const` `code` 修饰符 2. `__at__` 关键字后跟括号内的绝对地址 3. 地址必须是 16 位常量(0x0000-0xFFFF) 4. 初始化值在定义时直接赋值[^2] #### 方法 2:使用 GCC 风格的 `__attribute__`(兼容 ARM 编译器) ```c const uint8_t code MyArray[10] __attribute__((at(0x1000))) = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A }; ``` **适用场景**: - 需要跨平台兼容时(如同时支持 C51 ARM 项目) - 使用 Keil AC6 编译器时(需在 `Options for Target → C/C++(AC6)` 启用 C11/GNU11 支持)[^4] #### ⚠️ 重要注意事项 1. **地址冲突**:确保目标地址未被其他代码占用 2. **地址对齐**:数组起始地址应符合数据类型对齐要求 ```c // 错误示例:32位数据要求4字节对齐 const uint32_t code Data __at__ (0x1001); // ❌ 地址非4对齐 ``` 3. **初始化要求**:必须同时声明初始化数组 4. **调试验证**:编译后查看 .MAP 文件确认定位结果: ``` SYMBOL TABLE OF MODULE: MAIN (MAIN) VALUE TYPE NAME ----- ---- ---- C:0x1000 CODE MyArray // 确认地址为0x1000 ``` #### 应用场景示例 ```c // 将引导标志定位到固定地址 #define BOOT_FLAG_ADDR 0x7FF0 const uint32_t code BootFlag __at__ (BOOT_FLAG_ADDR) = 0x5A5AA5A5; // 将配置参数表定位到特定区域 const uint8_t code ConfigTable[256] __at__ (0xF000) = { // ... 256字节配置数据 ... }; ``` > **提示**:函数无法用 `__at__` 定位[^1],需要通过链接器脚本或项目选项中的 `BL51 Locate` 功能实现函数地址定位。 [^1]: keil下代码的定位1 [^2]: KEIL将一个变量、数组定义到指定Flash地址 [^3]: [学习笔记] KEIL 如何把data,code放到指定地址 [^4]: Keil的一点使用技巧
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值