zoukankan      html  css  js  c++  java
  • arm-linux-ld

    直接指定代码段,数据段,BSS段的起始地址

    -Ttext startaddr
    -Tdata startaddr
    -Tbss startaddr

    示例:

    arm-linux-ld –Ttext 0x0000000 led.o –o led.elf

     一下摘自Linux公社ld指令详解

    我们对每个c或者汇编文件进行单独编译,但是不去连接,生成很多.o 的文件,这些.o文件首先是分散的,我们首先要考虑的如何组合起来;其次,这些.o文件存在相互调用的关系;再者,我们最后生成的bin文件是要在硬件中运行的,每一部分放在什么地址都要有仔细的说明。我觉得在写makefile的时候,最为重要的就是ld的理解,下面说说我的经验:

    首先,要确定我们的程序用没有用到标准的c库,或者一些系统的库文件,这些一般是在操作系统之上开发要注意的问题,这里并不多说,熟悉在Linux编程的人,基本上都会用ld命令;这里,我们从头开始,直接进行汇编语言的连接。

    我们写一个汇编程序,控制GPIO,从而控制外接的LED,代码如下;

     1    .text
     2 
     3 .global _start
     4 
     5 _start:
     6 
     7     LDR R0,=0x56000010 @GPBCON寄存器
     8    
     9     MOV R1,# 0x00000400
    10     str R1,[R0]
    11    
    12     LDR R0,=0x56000014
    13     MOV R1,#0x00000000
    14    
    15     STR R1,[R0]
    16    
    17     MAIN_LOOP:
    18             B MAIN_LOOP

    代码很简单,就是一个对io口进行设置然后写数据。我们看它是如何编译的,注意我们这里使用的不是arm-linux-gcc而是arm-elf- gcc,二者之间没有什么比较大的区别,arm-linux-gcc可能包含更多的库文件,在命令行的编译上面是没有区别。我们来看是如何编译的:

           arm-elf-gcc -g -c -o led_On.o led_On.s  首先纯编译不连接

           arm-elf-ld  -Ttext 0x00000000 -g led_On.o -o led_on_elf

           用Ttext指明我们程序存储的地方,这里生成的是elf文件,还不是我们真正的bin,但是可以借助一些工具可以进行调试。然后:

           arm-elf-objcopy -O binary -S led_on_elf led_on.bin  

    生成bin文件。

    -T选项是ld命令中比较重要的一个选项,可以用它直接指明代码的代码段、数据段、BSS、

    段,对于复杂的连接,可以专门写一个脚本来告诉编译器如何连接。

        -Ttext   addr

        -Tdata  addr

        -Tbss     addr

    arm-elf-ld  -Ttext 0x00000000 -g led_On.o -o led_on_elf  ,运行地址为0x00000000,由于没有指明数据段和bss,他们会默认的依次放在后面。相同的代码不同的Ttext,你可以对比一下他们之间会变的差异,ld会自动调整跳转的地址。

    第二个概念:section,section可以理解成一块,例如像c里面的一个子函数,就是一个section,链接器ld把object文件中的每个section都作为一个整体,为其分配运行的地址(memory layout),这个过程就是重定位(relocation);最后把所有目标文件合并为一个目标文件。

    链接通过一个linker script来控制,这个脚本描述了输入文件的sections到输出文件的映射,以及输出文件的memory layout。

    因此,linker总会使用一个linker script,如果不特别指定,则使用默认的script;可以使用‘-T’命令行选项来指定一个linker script。

    *映像文件的输入段与输出段

    linker把多个输入文件合并为一个输出文件。输出文件和输入文件都是目标文件(object file),输出文件通常被称为可执行文件(executable)。

    每个目标文件都有一系列section,输入文件的section称为input section,输出文件的section则称为output section。

    一个section可以是loadable的,即输出文件运行时需要将这样的section加载到memory(类似于RO&RW段);也可以是 allocatable的,这样的section没有任何内容,某些时候用0对相应的memory区域进行初始化(类似于ZI段);如果一个 section既非loadable也非allocatable,则它通常包含的是调试信息。

    每个loadable或 allocatable的output section都有两个地址,一是VMA(virtual memory address),是该section的运行时域地址;二是LMA(load memory address),是该section的加载时域地址。

    可以通过objdump工具附加'-h'选项来查看目标文件中的sections。

    *简单的Linker script

    (1) SECTIONS命令:

    The SECTIONS command tells the linker how to map input sections into output sections, and how to place the output sections in memory.

    命令格式如下:

    SECTIONS
    {
    sections-command
    sections-command
    ......
    }
    

    其中sections-command可以是ENTRY命令,符号赋值,输出段描述,也可以是overlay描述。

    (2) 地址计数器‘.’(location counter):

    该符号只能用于SECTIONS命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。它会自动根据SECTIONS命令内部所描述的输出段的大小来计算当前的地址。

    (3) 输出段描述(output section description):

    前面提到在SECTIONS命令中可以作输出段描述,描述的格式如下:

    1 section [address] [(type)] : [AT(lma)]
    2 {
    3 output-section-command
    4 output-section-command
    5 ...
    6 } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

    很多附加选项是用不到的。其中的output-section-command又可以是符号赋值,输入段描述,要直接包含的数据值,或者某一特定的输出段关键字。

    *linker script 实例

    OUTPUT_ARCH(arm)
    ENTRY(_start)
    SECTIONS {
        . = 0xa3f00000;
        __boot_start = .;
        .start ALIGN(4) : {
            *(.text.start)
        }
        .setup ALIGN(4) : {
            setup_block = .;
            *(.setup)
            setup_block_end = .;
        }
    
        .text ALIGN(4) : {
            *(.text)
        }
    
        .rodata ALIGN(4) : {
            *(.rodata)
        }
    
        .data ALIGN(4) : {
            *(.data)
        }
    
        .got ALIGN(4) : {
            *(.got)
        }
    
        __boot_end = .;
        .bss ALIGN(16) : {
            bss_start = .;
            *(.bss)
            *(COMMON)
            bss_end = .;
    
        }
    
        .comment ALIGN(16) : {
            *(.comment)
        }
        stack_point = __boot_start + 0x00100000;
        loader_size = __boot_end - __boot_start;
        setup_size = setup_block_end - setup_block;
    }
    
    //============================= 
    //在SECTIONS命令中的类似于下面的描述结构就是输出段描述:
    
    .start ALIGN(4) : {
        *(.text.start)
    }

    .start 为output section name,ALIGN(4)返回一个基于location counter(.)的4字节对齐的地址值。*(.text.start)是输入段描述,*为通配符,意思是把所有被链接的object文件中的.text.start段都链接进这个名为.start的输出段。

    源文件中所标识的section及其属性实际上就是对输入段的描述,例如.text.start输入段在源文件start.S中的代码如下:

    .section .text.start

    .global _start

    _start :

        b start

    arm-elf-ld -Ttimer.lds -o timer_elf header .o

    这里就必须存在一个timer.lds的文件。

    对于 .lds 文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。

    先看一下 GNU 官方网站上对 .lds 文件形式的完整描述:

    SECTIONS {
    ...
    secname start BLOCK( align ) (NOLOAD) : AT ( ldadr )
      { contents } > region : phdr = fill
    ...
    } 

    secname 和contents 是必须的,其他的都是可选的。下面挑几个常用的看看:

    1 、 secname :段名

    2 、 contents :决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)

    3 、 start :本段连接(运行)的地址,如果没有使用 AT ( ldadr ),本段存储的地址也是 start 。 GNU 网站上说 start 可以用任意一种描述地址的符号来描述。

    4 、 AT ( ldadr ):定义本段存储(加载)的地址。

    /* nand.lds */
    SECTIONS {
    firtst 0x00000000 : { head.o init.o }
    second 0x30000000 : AT(4096) { main.o }
    }

        以上, head.o 放在 0x00000000 地址开始处, init.o 放在 head.o 后面,他们的运行地址也是 0x00000000 ,即连接和存储地址相同(没有 AT 指定); main.o 放在 4096 ( 0x1000 ,是 AT 指定的,存储地址)开始处,但是它的运行地址在 0x30000000 ,运行之前需要从 0x1000 (加载处)复制到 0x30000000 (运行处),此过程也就用到了读取 Nand flash 。

    这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在 .lds 连接脚本文件中分别指定。

    编写好的 .lds 文件,在用 arm-linux-ld 连接命令时带 -Tfilename 来调用执行,如
    arm-linux-ld –Tnand.lds x.o y.o –o xy.o 。也用 -Ttext 参数直接指定连接地址,如
    arm-linux-ld –Ttext 0x30000000 x.o y.o –o xy.o 。

    既然程序有了两种地址,就涉及到一些跳转指令的区别,这里正好写下来,以后万一忘记了也可查看,以前不少东西没记下来现在忘得差不多了。

    ARM 汇编中,常有两种跳转方法: b 跳转指令、 ldr 指令向 PC 赋值。

    我自己经过归纳如下:

    b step1 : b 跳转指令是相对跳转,依赖当前 PC 的值,偏移量是通过该指令本身的 bit[23:0] 算出来的,这使得使用 b 指令的程序不依赖于要跳到的代码的位置,只看指令本身。

    ldr pc, =step1 :该指令是从内存中的��个位置( step1 )读出数据并赋给 PC ,同样依赖当前 PC 的值,但是偏移量是那个位置( step1 )的连接地址(运行时的地址),所以可以用它实现从 Flash 到 RAM 的程序跳转。

    此外,有必要回味一下 adr 伪指令, U-boot 中那段 relocate 代码就是通过 adr 实现当前程序是在 RAM 中还是 flash 中。仍然用我当时的注释

      adr r0, _start /* r0 是代码的当前位置 */
    /* adr 伪指令,汇编器自动通过当前 PC 的值算出 如果执行到 _start 时 PC 的值,放到 r0 中:
    当此段在 flash 中执行时 r0 = _start = 0 ;当此段在 RAM 中执行时 _start = _TEXT_BASE( 在 board/smdk2410/config.mk 中指定的值为 0x33F80000 ,即 u-boot 在把代码拷贝到 RAM 中去执行的代码段的开始 ) */

      ldr r1, _TEXT_BASE /* 测试判断是从 Flash 启动,还是 RAM */
    /* 此句执行的结果 r1 始终是 0x33FF80000 ,因为此值是又编译器指定的 (ads 中设置,或 -D 设置编译器参数 ) */
        cmp r0, r1 /* 比较 r0 和 r1 ,调试的时候不要执行重定位 */

       下面,结合 u-boot.lds 看看一个正式的连接脚本文件。这个文件的基本功能还能看明白,虽然上面分析了好多,但其中那些 GNU 风格的符号还是着实让我感到迷惑。

    OUTPUT_FORMAT("elf32­littlearm", "elf32­littlearm", "elf32­littlearm")
      ; 指定输出可执行文件是 elf 格式 ,32 位 ARM 指令 , 小端
    OUTPUT_ARCH(arm)
      ; 指定输出可执行文件的平台为 ARM
    ENTRY(_start)
      ; 指定输出可执行文件的起始代码段为 _start.
    SECTIONS
    {
            . = 0x00000000 ; 从 0x0 位置开始
            . = ALIGN(4) ; 代码以 4 字节对齐
            .text : ; 指定代码段
            {
              cpu/arm920t/start.o (.text) ; 代码的第一个代码部分
              *(.text) ; 其它代码部分
            }
            . = ALIGN(4)
            .rodata : { *(.rodata) } ; 指定只读数据段
            . = ALIGN(4);
            .data : { *(.data) } ; 指定读 / 写数据段
            . = ALIGN(4);
            .got : { *(.got) } ; 指定 got 段 , got 段式是 uboot 自定义的一个段 , 非标准段
            __u_boot_cmd_start = . ; 把 __u_boot_cmd_start 赋值为当前位置 , 即起始位置
            .u_boot_cmd : { *(.u_boot_cmd) } ; 指定 u_boot_cmd 段 , uboot 把所有的 uboot 命令放在该段 .
            __u_boot_cmd_end = .; 把 __u_boot_cmd_end 赋值为当前位置 , 即结束位置
            . = ALIGN(4);
            __bss_start = .; 把 __bss_start 赋值为当前位置 , 即 bss 段的开始位置
            .bss : { *(.bss) }; 指定 bss 段
            _end = .; 把 _end 赋值为当前位置 , 即 bss 段的结束位置

    懒惰不会让你一下子跌到 但会在不知不觉中减少你的收获; 勤奋也不会让你一夜成功 但会在不知不觉中积累你的成果 越努力,越幸运。
  • 相关阅读:
    解决:Could not resolve archetype org.apache.maven.archetypes
    Spring MVC配置MyBatis输出SQL
    Spring集成MyBatis 通用Mapper以及 pagehelper分页插件
    关于SpringMVC或Struts2接受参数接收不到的原因
    配置quartz启动时就执行一次
    ajaxFileUpload进行文件上传时,总是进入error
    spring mvc注入配置文件里的属性
    java中将一个文件夹下所有的文件压缩成一个文件
    flume failed to start agent because dependencies were not found in classpath
    ubuntu不能安装pip unable to install pip in unbuntu
  • 原文地址:https://www.cnblogs.com/Rainingday/p/7435349.html
Copyright © 2011-2022 走看看