链接脚本文件、C源文件、arm汇编源文件中 地址常量 如何相互引用

 这里用到了u-boot工程中的一些例子进行说明。这些地址常量就是为了在链接脚本lds文件和C语言文件(或者汇编文件)之间传递地址值,有两种方法:

1. 在链接脚本文件中定义地址常量,然后再C文件或者汇编文件中使用;(以下一、二为这种方式)

2.在C文件或者汇编文件中自定义空段,在空段中放入空数组(C文件),直接操作数组名就是空段的地址。(以下三、四为这种方式)

一、lds中的地址常量在C文件中调用

这里以清除.bss段为例,

__bss_start = .;
.bss : { *(.bss) }
_end = .;

 在.bss段(默认就存在的段,用于存放未初始化的全局变量),开头和结尾引入两个地址常量__bss_start 和 _end , 在C代码中可以直接使用这两个地址常量,需要先用extern进行声明,如下:

void clean_bss(void)
{
extern int __bss_start, _end;
int *p = &__bss_start;
   
for (; p < &_end; p++)
        *p = 0;
}

详细参见:https://blog.csdn.net/thisway_diy/article/details/101016296 

二、lds中的地址常量在汇编文件中调用

仍然以清除.bss段为例,只不过这次使用汇编代码清除.bss段,连接脚本如下(参考正点原子的例程):

SECTIONS{
    . = 0x87800000;
    .text :
    {
        objs/start.o
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}
    .data ALIGN(4) : {*(.data)}
    . = ALIGN(4);
    __bss_start = .;
    .bss ALIGN(4) : { *(.bss) *(.COMMON) }
    __bss_end = .;
}

在汇编中调用如下:

.globl _bss_start
_bss_start:
        .word __bss_start

.globl _bss_end
_bss_end:
        .word __bss_end

/* 复位中断 */
Reset_Handler:

    /*清除BSS段*/
    ldr r0, _bss_start
    ldr r1, _bss_end
    mov r2, #0
bss_loop:
    stmia r0!, {r2}
    cmp r0, r1
    ble bss_loop

可以看到要先将常量通过.word存放到内存中,然后再读入寄存器中进行操作,当然,由于__bss_start  __bss_end本来就是常量,所以也可以借助伪指令ldr直接读入寄存器,如下(详见:https://blog.csdn.net/zhoudengqing/article/details/41346679?utm_source=blogxgwz2):

    @++++clear the BSS section++++
    ldr     r2,=__bss_start
    ldr     r3,=__bss_end
    mov     r12,#0
bss_loop:
    cmp     r2,r3 
    stmltia r2!,{r12}
    blt     bss_loop  
    @----clear the BSS section----

三、C文件中的地址常量如何在lds文件中调用

其实lds是不能直接使用C文件中的地址常量的,要借助自定义段名来实现,如下__bss_end常量是C文件sections.c中定义的,如下:

char __bss_start[0] __attribute__((section(".__bss_start")));
char __bss_end[0] __attribute__((section(".__bss_end")));

可以看到__bss_end是一个长度为0的数组,作用只是用来记录地址,所以长度为0,使用 __attribute__((section(".__bss_end")))的作用是讲这个长度为0的数组放到段名为.__bss_end的段里面,由于数组长度为0,所以.__bss_end段同样不占用内存空间,.__bss_end段可以通过lds文件的配置,放在特定的位置,这里放在默认的.bss段结尾,这样我们就可以在c文件中通过数组的地址,来知道.bss段的实际结束地址,如下:gd->mon_len = (ulong)&__bss_end - (ulong)_start;  用来获取整个u-boot镜像的大小。

static int setup_mon_len(void)
{
#if defined(__ARM__) || defined(__MICROBLAZE__)
    gd->mon_len = (ulong)&__bss_end - (ulong)_start;
#elif defined(CONFIG_SANDBOX) || defined(CONFIG_EFI_APP)
    gd->mon_len = (ulong)&_end - (ulong)_init;
#elif defined(CONFIG_BLACKFIN) || defined(CONFIG_NIOS2)
    gd->mon_len = CONFIG_SYS_MONITOR_LEN;
#elif defined(CONFIG_NDS32)
    gd->mon_len = (ulong)(&__bss_end) - (ulong)(&_start);
#else
    /* TODO: use (ulong)&__bss_end - (ulong)&__text_start; ? */
    gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE;
#endif
    return 0;
}

在u-boot.lds文件中对.__bss_end段配置如下:

 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));
 }

.bss_start段可以看成一个外层的段,里面只放了一个内层的段,段名是.__bss_start,由于.__bss_start段没有内容,所以.bss_start也没有内容,只是为了在C文件中能过获取接下来的真正有用的段.bss段的起始地址。

可以看到.bss段是真正存放未初始化全局变量的段,接下来又有一个.bss_end段,里面的内层段就是上述我们C代码中提到的段.__bss_end段。

.bss_start 和.bss_end都不占内存,空间排布上一个位于.bss段开头,一个位于.bss段结尾, 作用是把.bss段的开始地址和结束地址传递给C程序。

四、汇编文件中的地址常量如何在lds文件中调用

汇编文件中地址常量和C文件类似,也是借助段名来让lds文件使用,如下vectors.S文件中

.globl _start

/*
 *************************************************************************
 *
 * Vectors have their own section so linker script can map them easily
 *
 *************************************************************************
 */

	.section ".vectors", "ax"

定义了段名为.vectors的段,这里.section伪指令就不详解了,可以参考https://www.iteye.com/blog/xp9802-2012231

段.vectors在u-boot.lds文件中的调用如下:

 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4);
 .data : {
  *(.data*)
 }

在外层段.text(默认就有的段)中,第一个内层段是是.__image_copy_start段(C源码中自定义的段),第二个内层段是.vectors,这个是上述汇编代码中定义的段,接下来第三个内存段是start.o文件的.text段,*表示所有start.o文件中所有已.text开头的段(文本段),第四个是其他所有汇编文件、C文件中的.text段(文本段),其实.__image_copy_start段和.vectors段都不占空间,只是为了为汇编程序或者C程序提供源码的开头地址,然后将arch/arm/cpu/armv7/start.o文件中的文本段单独列出来,是为了将中断向量表放在整个代码的最开头,接下来才是其他所有文件的文本段。

下面再举一个例子:linux内核仓库  arch/arm/boot/compressed/head.S  115行,如下:

		.section ".start", #alloc, #execinstr
/*
 * sort out different calling conventions
 */
		.align
		/*
		 * Always enter in ARM state for CPUs that support the ARM ISA.
		 * As of today (2014) that's exactly the members of the A and R
		 * classes.
		 */
 AR_CLASS(	.arm	)
start:
		.type	start,#function
		.rept	7
		mov	r0, r0
		.endr
   ARM(		mov	r0, r0		)
   ARM(		b	1f		)
 THUMB(		adr	r12, BSYM(1f)	)
 THUMB(		bx	r12		)

		.word	_magic_sig	@ Magic numbers to help the loader
		.word	_magic_start	@ absolute load/run zImage address
		.word	_magic_end	@ zImage end address
		.word	0x04030201	@ endianness flag

 THUMB(		.thumb			)

.section ".start" 创建了一个.start段,start函数位于这个段。

arch/arm/boot/compressed/vmlinux.lds 45行开始,如下:

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
    /*
     * Discard any r/w data - this produces a link error if we have any,
     * which is required for PIC decompression.  Local data generates
     * GOTOFF relocations, which prevents it being relocated independently
     * of the text/got segments.
     */
    *(.data)
  }
  . = 0;
  _text = .;
  .text : {
    _start = .;
    *(.start)
    *(.text)
    *(.text.*)
    *(.fixup)
    *(.gnu.warning)
    *(.glue_7t)
    *(.glue_7)
  }
  .rodata : {
    *(.rodata)
    *(.rodata.*)
  }

ENTRY(_start)指令用于指定整个zImage以_start地址常量指定的地址为入口地址,_start在.text段中被赋值 _start = .;

接下来就是*(.start),.start段在arch/arm/boot/compressed/head.S中定义,前面已经提过,所以可以推出_start地址常量就是.start的地址,也就是arch/arm/boot/compressed/head.S中start函数的地址。

所以整个zImage以arch/arm/boot/compressed/head.S中start函数开头。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值