0%

从.C .S代码对应到内存位置:理解链接地址、运行地址、位置无关码、位置有关码

2020年5月6日 上午9:30

参考:

位置有关码和位置无关码详细解释_C/C++_shenlong1356的博客-CSDN博客
位置无关码、位置有关码、链接地址、加载地址_运维_漫不经心-CSDN博客
伪指令ADR和LDR的区别_嵌入式_墨的博客-CSDN博客
汇编LDR指令_网络_JadyC的博客-CSDN博客

1.核心概念总结

  1. 在移植 uboot 时,接触到一个概念叫做 位置无关码,那么与它对应的就是位置有关码。提到这两个概念就还得提一提链接地址、运行地址。
    1. 链接地址,链接脚本里指定的,理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。
    2. 运行地址,程序运行时,实际所处的地址。
  2. 位置无关码,位置有关码,是相对于一条指令的正常目的来说的。
    1. 比如 ldr r0 ,=标号,它的正常目的是取得标号处的地址,对于这个目的,它是位置有关码,运行的地址不对就获取不到正确的标号地址,其实它无论在哪都是获取的程序运行地址等于链接地址时,标号的地址,如果你就是想要这个值,那么用这条指令是非常正确的,就不用理会什么位置无关码,位置有关码的概念了,这一点非常重要。
    2. 因此,当运行地址不等于链接地址时,并不是不可以用位置无关码,而是要看你用位置无关码是否达到了你想要的目的。
  3. 位置无关码,依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令的正常目的,因此是位置无关的。
  4. 位置有关码,不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。

2.从代码实践分析

下面,我们来看常用的汇编指令以及C语言中哪些操作是位置有关码,哪些是位置无关码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 汇编
SECTIONS {
. = 0x33f80000;
.text : { *(.text) }

. = ALIGN(4);
.rodata : {*(.rodata*)}

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 汇编
.text
.global _start
_start:

bl close_watch_dog /* 相对跳转,位置无关 */
bl _start
adr r0, close_watch_dog /* 获取标号地址,位置无关 */

ldr r0, SMRDATA /* 获取标号处的值,位置无关 */

ldr r0, =0x12345678
ldr r0, =SMRDATA /* 获取标号地址,位置有关 */
ldr r0, =main /* 获取函数名的地址,位置有关 */
ldr r0 ,=__bss_start /* 获取链接脚本里标号的地址,位置有关 */


close_watch_dog:
mov r1, #0
str r1, [r0]
mov pc, lr

SMRDATA:
.word 0x22111120
1
2
3
4
5
6
7
8
9
10
11
int a;
void abc(){
a = 2;
}
int main(){
int b;
a =1 ;
b =1 ;
abc();
return 0;
}
编译后的反汇编代码:
  1. 编译后的反汇编代码如下,0x33f80000代表链接地址
  2. 如果我们把代码加载到0x33f80000处运行,此时运行的地址和链接地址完全一样,也就是下面的反汇编图
  3. 此时,链接地址 = 运行地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // 反汇编
    33f80000 <_start>:
    33f80000: eb000006 bl 33f80020 <close_watch_dog>
    33f80004: ebfffffd bl 33f80000 <_start>
    33f80008: e28f0010 add r0, pc, #16
    33f8000c: e59f0018 ldr r0, [pc, #24] ; 33f8002c <SMRDATA>
    33f80010: e59f0018 ldr r0, [pc, #24] ; 33f80030 <SMRDATA+0x4>
    33f80014: e59f0018 ldr r0, [pc, #24] ; 33f80034 <SMRDATA+0x8>
    33f80018: e59f0018 ldr r0, [pc, #24] ; 33f80038 <SMRDATA+0xc>
    33f8001c: e59f0018 ldr r0, [pc, #24] ; 33f8003c <SMRDATA+0x10>

    33f80020 <close_watch_dog>:
    33f80020: e3a01000 mov r1, #0
    33f80024: e5801000 str r1, [r0]
    33f80028: e1a0f00e mov pc, lr

    33f8002c <SMRDATA>:
    33f8002c: 22111120 andscs r1, r1, #8
    33f80030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000
    33f80034: 33f8002c mvnscc r0, #44 ; 0x2c
    33f80038: 33f80064 mvnscc r0, #100 ; 0x64
    33f8003c: 33f800a0 mvnscc r0, #160 ; 0xa0

    33f80040 <abc>:
    33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
    33f80044: e28db000 add fp, sp, #0
    33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
    33f8004c: e3a02002 mov r2, #2
    33f80050: e5832000 str r2, [r3]
    33f80054: e28bd000 add sp, fp, #0
    33f80058: e8bd0800 pop {fp}
    33f8005c: e12fff1e bx lr
    33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0

    33f80064 <main>:
    33f80064: e92d4800 push {fp, lr}
    33f80068: e28db004 add fp, sp, #4
    33f8006c: e24dd008 sub sp, sp, #8
    33f80070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38>
    33f80074: e3a02001 mov r2, #1
    33f80078: e5832000 str r2, [r3]
    33f8007c: e3a03001 mov r3, #1
    33f80080: e50b3008 str r3, [fp, #-8]
    33f80084: ebffffed bl 33f80040 <abc>
    33f80088: e3a03000 mov r3, #0
    33f8008c: e1a00003 mov r0, r3
    33f80090: e24bd004 sub sp, fp, #4
    33f80094: e8bd4800 pop {fp, lr}
    33f80098: e12fff1e bx lr
    33f8009c: 33f800a0 mvnscc r0, #160 ; 0xa0

    Disassembly of section .bss:

    33f800a0 <a>:
    33f800a0: 00000000 andeq r0, r0, r0
假设拷贝到内存之后对应的地址
  1. 如果我们把代码加载到0x00000000处运行,此时实际运行的情况应该是下面这样的:此时链接地址≠运行地址
  2. 注意:下面的不是反汇编的到的,反汇编得到的还是上面那样的,下面的只是我们根据实际运行情况描绘的
  3. 0x00000000代表的是实际运行地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // 反汇编
    00000000 <_start>:
    00000000: eb000006 bl 33f80020 <close_watch_dog>
    00000004: ebfffffd bl 33f80000 <_start>
    00000008: e28f0010 add r0, pc, #16
    0000000c: e59f0018 ldr r0, [pc, #24] ;
    00000010: e59f0018 ldr r0, [pc, #24] ;
    00000014: e59f0018 ldr r0, [pc, #24] ;
    00000018: e59f0018 ldr r0, [pc, #24] ;
    0000001c: e59f0018 ldr r0, [pc, #24] ;

    00000020 <close_watch_dog>:
    00000020: e3a01000 mov r1, #0
    00000024: e5801000 str r1, [r0]
    00000028: e1a0f00e mov pc, lr

    0000002c <SMRDATA>:
    0000002c: 22111120 andscs r1, r1, #8
    00000030: 12345678 eorsne r5, r4, #125829120 ; 0x7800000
    00000034: 33f8002c mvnscc r0, #44 ; 0x2c
    00000038: 33f80064 mvnscc r0, #100 ; 0x64
    0000003c: 33f800a0 mvnscc r0, #160 ; 0xa0

    00000040 <abc>:
    00000040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
    00000044: e28db000 add fp, sp, #0
    00000048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20>
    0000004c: e3a02002 mov r2, #2
    00000050: e5832000 str r2, [r3]
    00000054: e28bd000 add sp, fp, #0
    00000058: e8bd0800 pop {fp}
    0000005c: e12fff1e bx lr
    00000060: 33f800a0 mvnscc r0, #160 ; 0xa0

    00000064 <main>:
    00000064: e92d4800 push {fp, lr}
    00000068: e28db004 add fp, sp, #4
    0000006c: e24dd008 sub sp, sp, #8
    00000070: e59f3024 ldr r3, [pc, #36] ; 33f8009c <main+0x38>
    00000074: e3a02001 mov r2, #1
    00000078: e5832000 str r2, [r3]
    0000007c: e3a03001 mov r3, #1
    00000080: e50b3008 str r3, [fp, #-8]
    00000084: ebffffed bl 33f80040 <abc>
    00000088: e3a03000 mov r3, #0
    0000008c: e1a00003 mov r0, r3
    00000090: e24bd004 sub sp, fp, #4
    00000094: e8bd4800 pop {fp, lr}
    00000098: e12fff1e bx lr
    0000009c: 33f800a0 mvnscc r0, #160 ; 0xa0

代码分析:

一、BL指令
1
2
bl  close_watch_dog   //*位置无关码*
33f80000 : eb000006 bl 33f80020
  1. b 是相对跳转:跳转地址 = PC (PC=当前地址+8)+ 偏移值
  2. 偏移值:机器码 0xeb000006 低 24位 0x000006 按符号为扩展为 32 位 0x00000006 正数,向后跳转 0x6 * 4(32位位4字节)=(24)字节 也就是 0x18
  3. 起始运行地址为0x0000000时: 0x00000000(当前运行地址) + 8 + 0x18(偏移地址) = 0x00000020 跳转到正确位置
  4. 起始运行地址为0x3ff80000时: 0x3ff80000(当前运行地址) + 8 + 0x18(偏移地址) = 0x33f80020 跳转到正确位置
    二、ADR
    1
    2
    adr r0, close_watch_dog     /* 获取标号处的地址,*位置无关码*,伪指令 */
    33f80008: e28f0010 add r0, pc, #16
  5. 跳转地址 = PC (PC=当前地址+8)+ 偏移值
  6. 1、运行地址为0: 0 + 8 + 16 = 0x20 正确
  7. 2、运行地址为0x3ff80000: 0x3ff80008 + 8 + 16 = 0x33f80020 正确
  8. Adr 获取的是标号处的“实际”地址,标号在哪就是哪个地址,跟位置无关,总能获得想要的值。
    三、LDR
    LDR-1
    1
    2
    Ldr r0, SMRDATA       /* 获取标号处的值,*位置无关* *伪指令和下面的中间加上=号的不同**/
    33f8000c:e59f0018 ldr r0, [pc, #24];
  9. 起始运行地址地址0: r0 = c + 8 + 24 = 0x2c 处 22111120
  10. 起始运行地址0x3ff80000: r0 = 0x3ff8000c + 8 + 24 = 0x33f8002c 处 22111120 和上面得到的一样,所以和位置无关
    LDR-2
    1
    2
    Ldr r0, =SMRDATA     /* 获取标号地址,*位置有关* *这个中间加了= 不是伪指令,和上面的不同* */
    33f80014: e59f0018ldrr0, [pc, #24]; 33f80034 <SMRDATA+0x8>
  11. 起始运行地址0: r0 = 0x14 + 8 + 24 = 0x34 处的值 33f8002c 与实际运行地址0x0000002c不同 ,所以不正确
    1. 假设我要用这个地址去取值,或者用这个地址去跳转函数,而此时实际的变量或者函数根本不在链接地址处,这样肯定就出错了
  12. 起始运行地址0x3ff80000: r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 处的值33f8002c和实际运行地址33f8002c相同 正确
    LDR-3
    1
    2
    ldr r0, =main/* 获取函数名的地址,位置有关 */
    ldr r0 ,=__bss_start /* 获取链接脚本里标号的地址,位置有关 */
  13. 这俩和 ldr r0, =SMRDATA 一致,位置有关,在0地址处运行不正确。

C函数

1.全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int a;
void abc(){ //c语言函数abc()
a = 2;
}

//对c语言函数反汇编

33f80040 <abc>:
33f80040: e52db004 push {fp} ; (str fp, [sp, #-4]!)
33f80044: e28db000 add fp, sp, #0
33f80048: e59f3010 ldr r3, [pc, #16] ; 33f80060 <abc+0x20> //取出全局变量a
33f8004c: e3a02002 mov r2, #2
33f80050: e5832000 str r2, [r3] //对全局变量赋值2
33f80054: e28bd000 add sp, fp, #0
33f80058: e8bd0800 pop {fp}
33f8005c: e12fff1e bx lr
33f80060: 33f800a0 mvnscc r0, #160 ; 0xa0



33f800a0 <a>: //全局变量a
33f800a0: 00000000 andeq r0, r0, r0
  1. R3 为全局变量 a 的地址,无论a 是存放在 0起始的地址还是0x33f80000起始的地址,它都认为 a 的地址是 0x33f800a0 。因此,C函数中调用全局变量是位置有关码。
2.函数调用
1
33f80084: ebffffed bl  33f80040 <abc>
  1. 由于 main 函数和 abc 函数挨得比较近,在32M范围之内,因此被翻译成了一条 bl 指令那么与位置无关。
  2. 如果,调用的函数比较远,大于32M的话,我认为是与位置有关系的,这个不再验证了。
3.局部变量

局部变量在函数刚开始的地方被压入栈,赋值语句被翻译成:

1
2
33f8007c: e3a03001 movr3, #1
33f80080: e50b3008 str r3, [fp, #-8]