链接脚本文件的写法

本文详细解析了.lds文件在程序编译链接过程中的关键作用,包括如何定义可执行程序的段存储位置,以及如何通过链接脚本指定加载和运行地址。同时,介绍了ARM汇编中的跳转指令区别,以及在U-boot和xloader等系统程序中.lds文件的具体应用。

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

对于.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):定义本段存储(加载)的地址。

看一个简单的例子:(摘自《2410完全开发》)

/* 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赋值。

我自己经过归纳如下:

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

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

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

relocate: /* 把U-Boot重新定位到RAM */
    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风格的符号还是着实让我感到迷惑,好菜啊,怪不得连被3家公司鄙视,自己鄙视自己。。。

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段的结束位置
}

本文中的所有代码版本都是基于ST的SpearPlus开发板的。

xloader是在系统上电之后,执行完ROM中的frimware后最先开始执行的用户程序,它的体积很小,执行的功能也很简单,主要是对系统时钟以及外部SDRAM进行初始化,初始化完成之后就检查Flash中的uboot image是否准备好,如果准备好了就将Flash中的uboot image根据image header中指定的load address加载到外部SDRAM中,然后就跳转到uboot执行代码。

这里,我试图从头开始,在源代码级别上来分析整个系统的引导过程。

像Xloader或者uboot之类的程序,并不像我们平常写的应用程序那样,程序的入口函数直接找main函数就行。对于这种系统程序,在最开始看代码,尤其是要找到最开始执行的代码的位置的时候,最好的一个方法就是找到整个工程的.lds文件,也就是链接脚本文件(linker loader script)。它定义了整个工程在编译之后的链接过程,以及各个输入目标文件中的各个段在输出目标文件中的分布。详细的关于lds文件的介绍可以参考 gnu的在线文档:http://sourceware.org/binutils/docs/ld/index.html。其中的第三节Linker Script对链接脚本文件进行了介绍。

现在,我们首先开看一看xloader.lds的代码:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(XLOADER_ENTRY)
SECTIONS
{
    . = 0x00000000;
    . = ALIGN(4);
    .text    :
    {
      ./obj/init.o    (.text)
      *(.text)
    }
 
    .rodata . :
        {
                *(.rodata)
        }
 
        . = ALIGN(4);
 
     
    .data : { *(.data) }
    . = ALIGN(4);
    .got : { *(.got) }
      
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}

下面,我们对这一段代码逐句进行分析。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
在GNU的文档中,是这么定义的:
OUTPUT_FORMAT(default, big, little),在链接的时候,如果使用了-EB的命令行参数,则使用这里的big参数指定的字节序,如果使用了-EL的命令行参数,则使用这里的little参数指定的字节序,如果没有使用任何命令行参数,则使用这里的default参数指定的字节序。
由xloader.lds中的定义可见,不管在链接的时候使用了何种命令行参数,输出的目标文件都是使用elf32-littlearm方式的字节序。

OUTPUT_ARCH(arm)
在GNU的文档中,是这么定义的:
OUTPUT_ARCH(bfdarch),也就是指定了目标的体系结构,在这里,SpearPlus内部使用的处理器核是arm926ejs的,因此体系结构也就是arm。

ENTRY(XLOADER_ENTRY)
在GNU的文档中,是这么定义的:
ENTRY(symbol)
There are several ways to set the entry point. The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
    * the `-e' entry command-line option;
    * the ENTRY(symbol) command in a linker script;
    * the value of the symbol start, if defined;
    * the address of the first byte of the `.text' section, if present;
    * The address 0. 
也就是说,ENTRY(XLOADER_ENTRY)定义了整个程序的入口处,也就是在标号XLOADER_ENTRY处。整个程序将从这里开始运行。

接下来的部分,是对整个输出目标文件中各个段的存储位置的定义。
在GNU的文档中,是这么定义的:
SECTIONS
     {
       sections-command
       sections-command
       ...
     }
对于其中的每一个sections-command,其完整的定义如下:
The full description of an output section looks like this:

     section [address] [(type)] :
       [AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)]
       {
         output-section-command
         output-section-command
         ...
       } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

Most output sections do not use most of the optional section attributes.
The whitespace around section is required, so that the section name is unambiguous. The colon and the curly braces are also required. The line breaks and other white space are optional. 
下面来看看xloader.lds中SECTIONS的定义:
SECTIONS
{
    /* location counter设置为0x00000000,其实由于在Makefile中的
     * 链接选项中使用了-Ttext $(TEXT_BASE),而TEXT_BASE=0xD2800B00
     * 因此,此处的设置其实是没有作用的,代码运行的时候将运行在TEXT_BASE地址
     */
    . = 0x00000000;
    . = ALIGN(4);    /* 四字节对齐 */
    /* 将所有输入目标文件中的.text段即代码段放在此处,并且,输出目标文件中的.text段中的
     * 开头部分存放init.o的.text段。也就是运行的第一条代码,也就是XLOADER_ENTRY
     * 标号对应的代码就在init.o当中
     */
    .text    :
    {
      ./obj/init.o    (.text)
      *(.text)
    }
 
    /* 紧接着.text段,存放所有输入目标文件中的.rodata段,也就是
     * 只读数据段。此处注意.rodata后跟着的.,这个.表示当前location counter,
     * 对应于上述完整描述sections中的[address]
     * 此处表示.rodata段紧接着.text段存放,而不用任何对齐
     */
    .rodata . :
        {
                *(.rodata)
        }
 
        . = ALIGN(4);    /* 四字节对齐 */
 
     
    /* 将所有输入目标文件中的.data读写数据段存储在此处
     * 所有全局手动初始化的变量存储在该段中,并且在输出目标文件中已经分配了存储空间
     */
    .data : { *(.data) }
    . = ALIGN(4);    /* 四字节对齐 */
    /* .got段是GLOBAL OFFSET TABLE,具体的作用还没有搞清楚 */
    .got : { *(.got) }
      
    . = ALIGN(4);    /* 四字节对齐 */
    /* .bss段的开始,所有全局未初始化变量的大小等信息存储在该段中
     * 但是在输出的目标文件中并不为这些变量分配存储空间,
     * 而是交给操作系统在初始化的时候分配内存,然后紧跟在.data段后面并初始化为零
     * 另外,此处还定义了两个标号分别表示.bss的开始和结束(也是整个目标文件的结束)
     */
    __bss_start = .;
    .bss : { *(.bss) }
    _end = .;
}

从这里,我们能够得到的最关键的信息是:整个程序的入口在标号XLOADER_ENTRY处,并且该标号定义在init.o目标文件中,因为整个最终的链接之后的目标文件中,位于最开头的就是init.o目标文件。
于是,我们可以根据这个线索来继续追踪整个的引导过程了。


参考文章:
对.lds连接脚本文件的分析
http://blog.youkuaiyun.com/tony821224/archive/2008/01/18/2051755.aspx
Documentation for binutils 2.18--ld
http://sourceware.org/binutils/docs/ld/index.html
.bss段和.data段的区别
http://www.w3china.org/blog/more.asp?name=FoxWolf&id=29997
什么是bss段
http://blog.youkuaiyun.com/bobocheng1231/archive/2008/02/23/2115289.aspx

<think>好的,我现在需要帮助用户解决如何更改monkeyrunner脚本文件的存储位置或者在猴子测试工具中指定不同的脚本文件路径的问题。首先,我得回忆一下monkeyrunner的基本用法和相关配置。 用户的问题主要涉及脚本路径的修改,可能有两种情况:一种是直接运行脚本时指定不同路径,另一种是在调用monkeyrunner工具时指定脚本的位置。我记得monkeyrunner是通过命令行执行的,通常的命令格式是monkeyrunner 脚本路径。所以,用户可能需要知道如何在命令行中指定不同的路径。 接下来,我应该考虑具体的步骤。比如,用户如果有一个脚本存放在D盘的某个目录下,他们需要运行这个脚本,那么命令应该是monkeyrunner D:/path/to/script.py。这时候需要提醒用户注意路径中的斜杠方向,可能需要用正斜杠或者双反斜杠,避免转义问题。 另外,用户可能还想知道如何在脚本内部引用其他路径的文件,比如测试数据或配置文件。这时候需要说明如何在Python脚本中使用相对路径或绝对路径来处理文件操作,比如open('data/config.txt')或者open('/absolute/path/to/config.txt')。 还有可能涉及到环境变量的问题,比如将monkeyrunner添加到系统PATH中,这样可以更方便地在任何位置运行命令,而不需要输入完整路径。例如,如果monkeyrunner.bat位于Android SDK的tools/bin目录下,用户可以将该目录添加到PATH环境变量中,之后直接使用monkeyrunner命令。 另外,可能需要提到如何通过批处理文件或shell脚本简化命令,特别是当用户经常切换不同路径的脚本时,可以编一个脚本来接受参数,自动调用monkeyrunner执行指定路径的脚本。 还需要考虑不同操作系统的路径差异,比如Windows和Linux在路径表示上的不同,提醒用户根据系统调整路径格式。例如,Windows中使用反斜杠,但在Python字符串中可能需要转义,或者使用原始字符串。 最后,确保回答结构清晰,分点说明,让用户能够逐步操作。同时,检查是否有相关的引用资料需要标注,比如示例代码的来源或者官方文档的链接,但根据用户提供的引用,目前只有两个关于Python示例的引用,可能与此问题关联不大,但如果有必要的话可以提及。 总结步骤应该是: 1. 在命令行中直接指定绝对路径或相对路径来运行脚本。 2. 在脚本内部处理文件路径时使用正确的路径写法。 3. 可选的环境变量配置和脚本封装方法。 4. 注意事项,如路径格式、转义字符和跨平台差异。 需要确保每个步骤都有具体的示例,比如命令行示例和代码片段,帮助用户理解如何操作。同时,提醒用户常见的错误,比如路径错误导致的文件未找到异常,以及如何解决这些问题。</think>### 更改`monkeyrunner`脚本文件路径的方法 #### 方法1:通过命令行直接指定路径 在运行`monkeyrunner`时,直接在命令中提供脚本的**绝对路径**或**相对路径**。例如: ```bash # Windows(使用绝对路径) monkeyrunner C:\test_scripts\my_monkeytest.py # Linux/macOS(使用相对路径) monkeyrunner ./scripts/monkeytest.py ``` 注意路径中的斜杠方向:Windows中建议用反斜杠`\`或双正斜杠`//`,Linux/macOS用正斜杠`/`[^1]。 #### 方法2:脚本内动态修改路径 在Python脚本中使用`os`模块处理路径: ```python import os # 获取当前脚本所在目录 script_dir = os.path.dirname(os.path.abspath(__file__)) # 构建新路径 new_script_path = os.path.join(script_dir, "subfolder/new_script.py") ``` 此方法可实现跨平台路径兼容性。 #### 方法3:封装为批处理/Shell脚本(自动化) 创建`run_test.bat`(Windows)或`run_test.sh`(Linux/macOS): ```bash # Windows示例 @echo off set SCRIPT_PATH=D:\project\test_scripts\login_test.py monkeyrunner %SCRIPT_PATH% ``` ```bash # Linux/macOS示例 #!/bin/bash SCRIPT_PATH="/home/user/project/tests/login_test.py" monkeyrunner $SCRIPT_PATH ``` #### 关键注意事项 1. **权限问题**:确保目标路径有读权限(尤其是移动设备路径) 2. **路径转义**:Windows中特殊字符需转义,例如:`monkeyrunner "C:\\Program Files\\test.py"` 3. **环境变量**:建议将`ANDROID_HOME`加入系统变量,方便调用SDK工具 #### 常见错误处理 - **`No such file`错误**:使用`os.path.exists()`验证路径 ```python import os if not os.path.exists("test_data.txt"): print("Error: 文件路径错误!") ``` - **设备路径指定**:ADB命令需要特别格式 ```python device_path = "/sdcard/test/" ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值