第28部分- Linux ARM汇编 编译/链接/加载

第28部分- Linux ARM汇编 编译/链接/加载

编译

编译参数

-march=armv7-a:指定编译时arm架构(即代码要运行的架构)。

-mfloat-abi=softfp:soft/softfp/hard。

soft(软浮点):表明不是用FPU硬件,使用GCC整数算术库来模拟浮点运算

-mfpu=neon-vfpv4,参数-mfpu就是用来指定要产生那种硬件浮点运算指令。常用的有vfpv3,vfpv4,neon等,hi3536 A17支持的是neon+vfpv4相结合的结构。

连接过程示例

模块是一种机制,通过这种机制,编程语言可以使用户将程序拆分为不同的逻辑部分。 模块化要求实现编程语言的工具提供一定程度的支持。 单独编译是实现此目的的机制。 在C语言中,程序可能会分解为几个源文件。 通常,编译C源文件会生成一个目标文件,因此,多个源文件将导致多个目标文件。 这些目标文件使用链接器进行组合。 链接器生成最终程序。

对于链接器,ELF可重定位文件是节的集合。 部分代表连续的数据块(可以是任何数据:指令,全局变量的初始值,调试信息等)。 每个部分都有一个名称和属性,例如是否必须在内存中分配,是否从映像(即包含程序的文件)中加载,是否可以执行,是否可写,大小和对齐方式等。

对象文件1

定义函数如下:

.balign 4

.text
.type mult_by_5,@function
.globl mult_by_5
mult_by_5: 
    add x0, x0, x0, LSL #2           /* r0 ← r0 + 4*r0 */
    ret

进行汇编如下:

as -g -o mult_by_5.o mult_by_5.s

查看mult_by_5.o

objdump -dr mult_by_5.o

mult_by_5.o:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000000000 <mult_by_5>:
   0:	8b000800 	add	x0, x0, x0, lsl #2
   4:	d65f03c0 	ret

对象文件2

主函数如下:

.data
.balign 4
message1: .asciz "Hey, type a number: "
 .balign 4
message2: .asciz "%d times 5 is %d\n"
/* Format pattern for scanf */
.balign 4
scan_pattern : .asciz "%d"
 
/* Where scanf will store the number read */
.balign 4
number_read: .word 0
 
.balign 4
return: .word 0
 
.balign 4
return2: .word 0
 
.text
.global _start
_start:
    ldr x0, address_of_message1      /* r0 ← &message1 */
    bl printf                        /* call to printf */
 
    ldr x0, address_of_scan_pattern  /* r0 ← &scan_pattern */
    ldr x1, address_of_number_read   /* r1 ← &number_read */
    bl scanf                         /* call to scanf */
 
    ldr x0, address_of_number_read   /* r0 ← &number_read */
    ldr x0, [x0]                     /* r0 ← *r0 */
    bl mult_by_5
 
    mov x2, x0                       /* r2 ← r0 */
    ldr x1, address_of_number_read   /* r1 ← &number_read */
    ldr x1, [x1]                     /* r1 ← *r1 */
    ldr x0, address_of_message2      /* r0 ← &message2 */
    bl printf                        /* call to printf */
      mov x8, 93
	svc 0

address_of_message1 : .dword message1
address_of_message2 : .dword message2
address_of_scan_pattern : .dword scan_pattern
address_of_number_read : .dword number_read
address_of_return : .dword return
 
/* External */
.global printf
.global scanf

as -g -o print64-f.o print64-f.s

得到print64-f.o对象文件。这里看下address_of_message1 : .dword message1

,这个是需要冲定向的。

通过#objdump -dr print64-f.o

查看发现如下:

print64-f.o:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000000000 <_start>:
   0:	580001e0 	ldr	x0, 3c <address_of_message1>
   4:	94000000 	bl	0 <printf>
			4: R_AARCH64_CALL26	printf
   8:	58000220 	ldr	x0, 4c <address_of_scan_pattern>
   c:	58000241 	ldr	x1, 54 <address_of_number_read>
  10:	94000000 	bl	0 <scanf>
			10: R_AARCH64_CALL26	scanf
  14:	58000200 	ldr	x0, 54 <address_of_number_read>
  18:	f9400000 	ldr	x0, [x0]
  1c:	94000000 	bl	0 <mult_by_5>
			1c: R_AARCH64_CALL26	mult_by_5
  20:	aa0003e2 	mov	x2, x0
  24:	58000181 	ldr	x1, 54 <address_of_number_read>
  28:	f9400021 	ldr	x1, [x1]
  2c:	580000c0 	ldr	x0, 44 <address_of_message2>
  30:	94000000 	bl	0 <printf>
			30: R_AARCH64_CALL26	printf
  34:	d2800ba8 	mov	x8, #0x5d                  	// #93
  38:	d4000001 	svc	#0x0

000000000000003c <address_of_message1>:
	...
			3c: R_AARCH64_ABS64	.data

0000000000000044 <address_of_message2>:
	...
			44: R_AARCH64_ABS64	.data+0x18

000000000000004c <address_of_scan_pattern>:
	...
			4c: R_AARCH64_ABS64	.data+0x2c

0000000000000054 <address_of_number_read>:
	...
			54: R_AARCH64_ABS64	.data+0x30

000000000000005c <address_of_return>:
	...
			5c: R_AARCH64_ABS64	.data+0x34

每种重定位类型均由一些参数定义:S是重定位所引用符号的地址(上面的VALUE),P是位置的地址(偏移量加上节本身的地址),A( for addenda)是汇编器保留的值。

连接成可执行函数,命令如下

ld -g -o print64-f print64-f.o mult_by_5.o -lc -I /lib64/ld-linux-aarch64.so.1

查看二进制文件

objdump -d print64-f


print64-f:     file format elf64-littleaarch64


Disassembly of section .plt:

0000000000400290 <.plt>:
  400290:	a9bf7bf0 	stp	x16, x30, [sp,#-16]!
  400294:	f00000f0 	adrp	x16, 41f000 <mult_by_5+0x1eccc>
  400298:	f947fe11 	ldr	x17, [x16,#4088]
  40029c:	913fe210 	add	x16, x16, #0xff8
  4002a0:	d61f0220 	br	x17
  4002a4:	d503201f 	nop
  4002a8:	d503201f 	nop
  4002ac:	d503201f 	nop

00000000004002b0 <scanf@plt>:
  4002b0:	90000110 	adrp	x16, 420000 <scanf@GLIBC_2.17>
  4002b4:	f9400211 	ldr	x17, [x16]
  4002b8:	91000210 	add	x16, x16, #0x0
  4002bc:	d61f0220 	br	x17

00000000004002c0 <printf@plt>:
  4002c0:	90000110 	adrp	x16, 420000 <scanf@GLIBC_2.17>
  4002c4:	f9400611 	ldr	x17, [x16,#8]
  4002c8:	91002210 	add	x16, x16, #0x8
  4002cc:	d61f0220 	br	x17

Disassembly of section .text:

00000000004002d0 <_start>:
  4002d0:	580001e0 	ldr	x0, 40030c <address_of_message1>
  4002d4:	97fffffb 	bl	4002c0 <printf@plt>
  4002d8:	58000220 	ldr	x0, 40031c <address_of_scan_pattern>
  4002dc:	58000241 	ldr	x1, 400324 <address_of_number_read>
  4002e0:	97fffff4 	bl	4002b0 <scanf@plt>
  4002e4:	58000200 	ldr	x0, 400324 <address_of_number_read>
  4002e8:	f9400000 	ldr	x0, [x0]
  4002ec:	94000012 	bl	400334 <mult_by_5>
  4002f0:	aa0003e2 	mov	x2, x0
  4002f4:	58000181 	ldr	x1, 400324 <address_of_number_read>
  4002f8:	f9400021 	ldr	x1, [x1]
  4002fc:	580000c0 	ldr	x0, 400314 <address_of_message2>
  400300:	97fffff0 	bl	4002c0 <printf@plt>
  400304:	d2800ba8 	mov	x8, #0x5d                  	// #93
  400308:	d4000001 	svc	#0x0

000000000040030c <address_of_message1>:
  40030c:	00420010 	.word	0x00420010
  400310:	00000000 	.word	0x00000000

0000000000400314 <address_of_message2>:
  400314:	00420028 	.word	0x00420028
  400318:	00000000 	.word	0x00000000

000000000040031c <address_of_scan_pattern>:
  40031c:	0042003c 	.word	0x0042003c
  400320:	00000000 	.word	0x00000000

0000000000400324 <address_of_number_read>:
  400324:	00420040 	.word	0x00420040
  400328:	00000000 	.word	0x00000000

000000000040032c <address_of_return>:
  40032c:	00420044 	.word	0x00420044
  400330:	00000000 	.word	0x00000000

0000000000400334 <mult_by_5>:
  400334:	8b000800 	add	x0, x0, x0, lsl #2
  400338:	d65f03c0 	ret

地址也可以通过objdump -h(加上标志-w使其更易读)来确定。 一个文件可能包含许多部分。

$objdump -hw print64-f

print64-f:     file format elf64-littleaarch64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn  Flags
  0 .interp       0000001d  0000000000400190  0000000000400190  00000190  2**0  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .hash         00000018  00000000004001b0  00000000004001b0  000001b0  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .dynsym       00000048  00000000004001c8  00000000004001c8  000001c8  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .dynstr       00000023  0000000000400210  0000000000400210  00000210  2**0  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gnu.version  00000006  0000000000400234  0000000000400234  00000234  2**1  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .gnu.version_r 00000020  0000000000400240  0000000000400240  00000240  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .rela.plt     00000030  0000000000400260  0000000000400260  00000260  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .plt          00000040  0000000000400290  0000000000400290  00000290  2**4  CONTENTS, ALLOC, LOAD, READONLY, CODE
  8 .text         0000006c  00000000004002d0  00000000004002d0  000002d0  2**2  CONTENTS, ALLOC, LOAD, READONLY, CODE
  9 .dynamic      00000140  000000000041fea0  000000000041fea0  0000fea0  2**3  CONTENTS, ALLOC, LOAD, DATA
 10 .got          00000008  000000000041ffe0  000000000041ffe0  0000ffe0  2**3  CONTENTS, ALLOC, LOAD, DATA
 11 .got.plt      00000028  000000000041ffe8  000000000041ffe8  0000ffe8  2**3  CONTENTS, ALLOC, LOAD, DATA
 12 .data         0000003c  0000000000420010  0000000000420010  00010010  2**2  CONTENTS, ALLOC, LOAD, DATA
 13 .debug_aranges 00000060  0000000000000000  0000000000000000  00010050  2**4  CONTENTS, READONLY, DEBUGGING
 14 .debug_info   00000094  0000000000000000  0000000000000000  000100b0  2**0  CONTENTS, READONLY, DEBUGGING
 15 .debug_abbrev 00000028  0000000000000000  0000000000000000  00010144  2**0  CONTENTS, READONLY, DEBUGGING
 16 .debug_line   0000008b  0000000000000000  0000000000000000  0001016c  2**0  CONTENTS, READONLY, DEBUGGING

VMA列定义该部分的地址。 在我们的例子中,.data位于0000000000420010 。我们的变量位于0042001000420028….。可以看到这要求链接器打印生成的可执行文件的映射。

 

映射文件

ld -g -o print64-f print64-f.o mult_by_5.o -lc -I /lib64/ld-linux-aarch64.so.1 --print-map

加载程序

在执行程序之前,需要将其放入内存。 此过程称为加载。 通常,操作系统负责加载程序。

 

链接发生后,地址在最终程序文件中进行了硬编码。 在这种情况下,加载程序任务非常简单,只需将程序文件的相关位复制到内存中即可。 地址已经由链接器固定,因此只要我们将程序复制(即加载)到正确的内存地址中,就可以完成。

    像Linux这样的现代操作系统,由于对此提供了特定的硬件支持,因此可以向进程(即正在运行的程序)提供所谓的虚拟内存。 虚拟内存给人一种幻觉,即进程可以根据需要使用内存空间。 此机制还提供隔离:一个进程无法写入另一个进程的内存。 运行几个要在同一地址加载的程序不是问题,因为它们只是在同一虚拟地址加载。 操作系统将这些虚拟地址映射到不同的物理地址。

 

位置无关代码

位置无关代码(称为PIC)不使用绝对地址。而是使用某种机制在程序上建立相对地址。在运行时,可以通过一些额外的计算将这些相对地址转换为绝对地址。 ELF中使用的机制使用称为全局偏移表的表。该表包含条目,每个我们要访问的全局实体一个条目。在运行时,每个条目将保存对象的绝对地址。每个程序和动态库都有自己的GOT,不会与其他任何人共享。该GOT以一种无需使用绝对地址即可访问它的方式位于内存中。为此,必须使用相对于PC的地址。因此,GOT然后位于某个固定位置,可以在静态链接时间计算出该位置与参考它的距离。

实际上,仅动态库才需要位置无关的代码。 我们的主程序仍然可以使用非PIC访问,并且适用于库中的变量,链接器将处理这种情况。 但是没有什么可以阻止我们在主程序中使用PIC代码。 位置无关可执行文件(PIE)需要通过GOT进行所有访问。

我们不能使用一种机制来强制代码进行重定位(即,固定其地址)。 只能固定GOT(毕竟不是代码)。

访问全局变量静态库示例

静态库如下,保存一个变量

/* mylib.s */
.data
.balign 4
.globl myvar
myvar : .dword 42     /* 42 as initial value */
.size myvar, .-myvar

main.s文件如下:

/* main.s */
.data
.globl myvar
 
.text
.globl main
.balign 4
main:
    ldr x0, addr_myvar  /* r0 ← &myvar */
    ldr x1, [x0]        /* r1 ← *r0 */
    add x1, x1, #1      /* r1 ← r1 + 1 */
    str x1, [x0]        /* *r0 ← r1 */
 
    mov x0, #0
    mov x8, 93
    svc 0
 
addr_myvar: .dword myvar

进行编译,

as -o mylib.o mylib.s
编译静态库如下:
ar cru mylib.a mylib.o
as -o main.o main.s
gcc -o main main.o -L. -l:mylib.a

将mylib.s编程成动态库

as -o mylib.o mylib.s
gcc -shared -o mylib.so mylib.o -fpic
as -g -o main.o main.s
gcc -g -o main main.o -L. -l:mylib.so -Wl,-rpath,$(pwd)

myvar的地址将在GOT中的某些条目中。 确切地说,我们不知道链接器是哪一个。 但是,我们仍然需要首先获取GOT的基地址。

PIC访问将是相对于pc的。 鉴于程序和库将作为单个片段加载到内存中,我们可以要求静态链接器为我们提供GOT的确切偏移量。

延迟加载

在延迟加载下,我们第一次调用函数时必须加载它。 进一步的调用将使用以前加载的函数。 这是有效的,但需要更多的机制。 在ELF中,这是通过使用称为过程链接表(PLT)的附加表来实现的。

与GOT相比,PLT是代码。 PLT中的条目是很小的指令序列,仅分支到GOT中的条目。 功能的GOT条目由动态链接器初始化,并带有动态链接器内部功能的地址,该内部链接器检索功能的地址,使用该地址更新GOT并分支到该地址。 因为动态链接器更新了GOT表,所以通过PLT进行的下一次调用(该调用只是分支到GOT)将直接转到该函数。

      为什么不直接调用GOT中的地址,或者为什么要使用PLT。 原因是动态链接器必须知道我们要第一次加载哪个函数,如果我们直接调用GOT中的地址,则需要设计一种机制来判断必须加载哪个函数。 一种方法是将函数的GOT条目初始化为准备所有内容的表,以便动态加载程序知道需要加载的确切函数。 但这实际上相当于PLT。

      所有这一切看起来都过于复杂,但是好消息是,链接器创建了这些PLT条目,它们可以用作常规函数调用。 无需获取GOT条目的地址以及我们对变量所做的所有事情(如果要使用该函数的地址,我们仍然必须这样做!)。 我们总是可以这样做,但这会使代码膨胀,因为每个函数调用都需要在GOT表中进行复杂的索引。 该机制适用于PIC和非PIC,这就是我们能够调用诸如printf之类的C库函数的原因,而不必担心它是否来自动态库(并且除非我们使用-static生成一个 完全静态可执行文件)。 也就是说,我们可以显式使用后缀@PLT表示我们要通过PLT调用函数。 对于在库内进行的调用,这是必需的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值