8 链接器 自定义链接脚本,指定入口函数,指定代码段起始地址等等

本文学习自狄泰软件学院 唐佐林老师的 操作系统课程,本文图片来源于唐佐林老师课程PPT,只用于个人笔记学习


  • 1 链接脚本的概念和意义
  • 2 链接脚本的本质
  • 3 链接脚本的写法和注意事项
  • 4 实验1 : 自定义的链接脚本中自己指定的 代码段和数据段的地址没有遵循linux平台对应用程序的规定
  • 5 实验2 :链接脚本中指定全局变量地址
  • 6 实验3 :在链接脚本中定义标识符,并且在源码中使用
  • 7 Memory 命令 : 桌面环境的链接方式 VS 嵌入式环境的链接方式
  • 8 实验4 通过链接脚本指定应用程序的入口函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实验1 : 自定义的链接脚本中自己指定的 代码段和数据段的地址没有遵循linux平台对应用程序的规定。

test.c

#include <stdio.h>

int s1;

int main()
{
    printf("&s1 = %p\n", &s1);
    
    return 0;
}

test.lds

SECTIONS
{
    .text 0x3000000:
    {
        *(.text)
    }
    
    .data 0x4000000:
    {
        *(.data)
    }
    
    .bss :
    {
        *(.bss)
    }
}

以默认的方式进行编译,然后运行:

mhr@ubuntu:~/work$ gcc -o test.out test.c
mhr@ubuntu:~/work$ ./test.out 
&s1 = 0x60103c
mhr@ubuntu:~/work$ 

使用自定义链接脚本进行编译

在这里插入图片描述

使用链接脚本编译运行后提示段错误,因为我们自定义的链接脚本中自己指定的 代码段和数据段的地址没有遵循linux平台对应用程序的规定。


实验2 :链接脚本中指定全局变量地址

test.c

#include <stdio.h>

int s1;

int main()
{
    printf("&s1 = %p\n", &s1);
    
    return 0;
}

test.lds

SECTIONS
{
    .text 0x08048400:  说明:符合平台规定
    {
        *(.text)
    }
    
    . = 0x01000000;  说明:将当前位置指针 . 的值改变为 0x01000000
    
    s1 = .;  显示的重定位 s1这个标识符,即s1就是 0x01000000 地址的别名,即s1这个全局变量的地址位于 0x01000000
    
    .data 0x0804a800:  说明:符合平台规定
    {
        *(.data)
    }
    
    .bss :  
    {
        *(.bss)
    }
}

使用自定义链接脚本编译并且运行

mhr@ubuntu:~/work$ gcc -o test-lds.out test.c test.lds
mhr@ubuntu:~/work$ ./test-lds.out 
&s1 = 0x1000000  
mhr@ubuntu:~/work$ 

果然s1这个全局变量的地址就是链接脚本中指定的 地址 0x1000000


实验3 :在链接脚本中定义标识符,,并且在源码中使用

test.c

#include <stdio.h>

int s1;
extern int s2;//使用标识符s2  在链接脚本中定义

int main()
{
    printf("&s1 = %p\n", &s1);
    printf("&s2 = %p\n", &s2);
    
    return 0;
}

test.lds

SECTIONS
{
    .text 0x08048400:
    {
        *(.text)
    }
    
    . = 0x01000000;
    
    s1 = .;
    
    . += 4;  重新指定当前位置指针.的值
    
    s2 = .;  用改变后的当前位置指针的值来重定位s2这个标识符,即s2是 0x1000004地址的别名
    
    .data 0x0804a800:
    {
        *(.data)
    }
    
    .bss :
    {
        *(.bss)
    }
}

使用自定义链接脚本编译并且运行

mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc -o test-lds.out test.c test.lds 
mhr@ubuntu:~/work$ ./test-lds.out 
&s1 = 0x1000000
&s2 = 0x1000004
mhr@ubuntu:~/work$ 

在这里插入图片描述

自从有了MMU内存管理单元之后,编译,链接的时候所面的都是虚存地址,具体的存储区域在内存的什么地方是由mmu决定的,所以链接器就认为这些事情全部由mmu做就可以了,自己在链接的时候只需要给一个合法的重定位地址就可以了。虚存地址,就是逻辑上的一个非常大的存储空间,在桌面应用中,链接器这个认为大多数是对的,但是对于嵌入式环境而言,就有可能不存在mmu,直接基于物理存储空间开发,此时如果存在多个存储空间…

在这里插入图片描述
嵌入式环境的链接方式,在链接脚本中,通过memory 指令 指定 代码段放在只读属性的flash中直接运行,数据段放在Ram中。
在这里插入图片描述

在这里插入图片描述


通过链接脚本指定应用程序的入口函数
在这里插入图片描述

实验4 :通过链接脚本指定应用程序的入口函数

test.c

#include <stdio.h>
#include <stdlib.h>

int program()
{
    printf("D.T.Software\n");
    
    exit(0);
}

test.lds : 链接器根据平台规范来确认其他段的信息,此处之编写代码段的信息,并且显示的重定位代码段到地址 0x08048400

ENTRY(program)

SECTIONS
{
    .text 0x08048400:
    {
        *(.text)
    }
}

编译链接运行

mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc -o test.o -c  test.c
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc -nostartfiles test.o -o test-lds.out test.lds
mhr@ubuntu:~/work$ ./test-lds.out
D.T.Software
mhr@ubuntu:~/work$ 


mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ nm test-lds.out 
0000000008249028 D __bss_start
0000000008248ec0 d _DYNAMIC
0000000008249028 D _edata
0000000008249028 D _end
                 U exit@@GLIBC_2.2.5
0000000008249000 d _GLOBAL_OFFSET_TABLE_
0000000008048428 r __GNU_EH_FRAME_HDR
0000000008048400 T program   //program   函数的地址 0000000008048400 
                 U puts@@GLIBC_2.2.5
                 U _start
mhr@ubuntu:~/work$ 


mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ objdump -h test-lds.out 

test-lds.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
 ...
//所以 grogram确实被指定成了 代码段的入口函数
  9 .text         00000018  0000000008048400  0000000008048400  00048400  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
...
mhr@ubuntu:~/work$ mhr@ubuntu:~/work$ 

在这里插入图片描述

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
在单片机中,程序的内存空间通常也被分为多个,包括代码、数据、堆、栈等。以下是一些常用的方法来确定这些地址范围: 1. 代码地址范围:代码通常存放程序的指令代码,可以通过查看芯片手册或者编译链接脚本来确定代码地址范围。一般情况下,代码起始地址是固定的,可以通过芯片手册中的地址映射图来确定。 2. 数据地址范围:数据通常存放程序的静态变量和全局变量,可以通过查看编译链接脚本来确定数据地址范围。一般情况下,数据起始地址也是固定的,可以通过芯片手册中的地址映射图来确定。 3. 堆地址范围:堆是程序在运行时动态分配内存的区域,可以通过使用malloc、calloc等函数来分配内存。在单片机中,由于内存容量有限,一般不会使用堆。如果确实需要使用堆,可以通过查看编译链接脚本和芯片手册中的地址映射图来确定堆的地址范围。 4. 栈地址范围:栈是程序在运行时使用的一种数据结构,用来保存函数的局部变量和函数调用的上下文信息。在单片机中,栈的大小一般是固定的,可以通过查看编译链接脚本来确定栈的地址范围。 总的来说,确定单片机中各个地址范围需要一定的芯片手册和编译知识,同时也需要一定的调试能力和工具支持。在实际开发中,可以使用调试、性能分析工具、内存分析工具等来帮助确定各个地址范围。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux老A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值