Linux的ELF文件装载内存布局

Linux的ELF文件装载内存布局

1.编译链接源文件

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int A;
int B = 0;
int C = 2;
static int D;
static int E = 0;
static int F = 2;
const int G = 5;

int main() {
int a;
int b = 0;
int c = 2;
static int d;
static int e = 0;
static int f = 2;
const int g = 5;
char char1[] = "abcde";
char *p = "1";
int *heap = malloc(sizeof(int) * 4);

printf("PID is %d\n\n", getpid());
printf("int A A_addr:%p\n", &A);
printf("int B = 0 B_addr:%p\n", &B);
printf("int C = 2 C_addr:%p\n", &C);
printf("static int D D_addr:%p\n", &D);
printf("static int E = 0 E_addr:%p\n", &E);
printf("static int F = 2 F_addr:%p\n", &F);
printf("const int G = 5 G_addr:%p\n", &G);
printf("int a a_addr:%p\n", &a);
printf("int b = 0 b_addr:%p\n", &b);
printf("int c = 2 c_addr:%p\n", &c);
printf("static int d d_addr:%p\n", &d);
printf("static int e = 0 e_addr:%p\n", &e);
printf("static int f = 2 f_addr:%p\n", &f);
printf("const int g = 5 g_addr:%p\n", &g);
printf("char char1[] = abcd char1_addr:%p\n", char1);
printf("char *p p_addr:%p\n", &p);
printf("value of p p_value:%p\n", p);

pause();
free(heap);
return 0;

}

编译链接形成ELF可执行文件,查看执行效果(打印各个变量/常量的内存地址信息):

image-20230718115031008-1689691400240-1

2.查看进程的内存布局

image-20230718115005104-1689691400241-2

重点关注上面的前三个(可读可执行|只读|可读可写区)和heap区和stack区,没有显示名称的是匿名映射。

image-20230718115351307-1689691400242-3

结合以上,总结:全局变量和静态变量存放在可读可写段,其中未初始化的存放在.bss,初始化的存放在.data,全局常量存放在.rodata中,函数内部的局部变量/局部常量存放在栈中,动态申请的内存存在堆中(main函数中的heap指针本身存放在栈中)

3.查看ELF文件的段节信息

image-20230718120206781-1689691400242-6

image-20230718120459068-1689691400242-4

第一张图中ELF文件两个Segment需要我们关注,就是LOAD类型的段,其各自包含了若干个不同的Section。值得一提的是,第二个LOAD类型的段的MemSize为0x00150,FileSize为0x00134,两者相差28字节的大小,而这正是.bss节的大小,.bss节内容全部初始化为0。

image-20230718123442619-1689691400242-7

4.编写可加载内核模块

编写内核模块:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/sched.h>

static pid_t pid;

/* 向模块传递参数,文件权限是0664 */
module_param(pid, int, 0664);

int print_vma(void) {
struct task_struct *task;
struct mm_struct *mm;
struct vm_area_struct *vma;
char perm[5] = {'\0'};
printk("begin to print virtual address space...\n");
task = pid_task(find_vpid(pid), PIDTYPE_PID);
mm = task->mm;

/* task_struct */
printk("executable name:%s pid:%d\n", task->comm, task->pid);

/* mm_struct */
/* .text & .data */
printk("start_code:0x%lx end_code:0x%lx\n", mm->start_code, mm->end_code);
printk("start_data:0x%lx end_data:0x%lx\n", mm->start_data, mm->end_data);

/* heap */
printk("start_brk:0x%lx end_brk:0x%lx\n", mm->start_brk, mm->brk);

/* stack */
printk("start_stack:0x%lx\n", mm->start_stack);
printk("\n");

/* vma_struct */
down_read(&mm->mmap_sem);
/* vma is a linklist */
for(vma = task->mm->mmap; vma; vma = vma->vm_next) {
// printk("0x%lx - 0x%lx ", vma->vm_start, vma->vm_end);
if(vma->vm_flags & VM_READ)
// printk("r");
perm[0] = 'r';
else
// printk("-");
perm[0] = '-';
if(vma->vm_flags & VM_WRITE)
// printk("w");
perm[1] = 'w';
else
// printk("-");
perm[1] = '-';
if(vma->vm_flags & VM_EXEC)
// printk("x");
perm[2] = 'x';
else
// printk("-");
perm[2] = '-';
if(vma->vm_flags & VM_SHARED)
// printk("s");
perm[3] = 's';
else
// printk("p");
perm[3] = 'p';
printk("0x%lx - 0x%lx %s\n", vma->vm_start, vma->vm_end, perm);
}
up_read(&mm->mmap_sem);
return 0;
}

static int __init print_vma_init(void) {
print_vma();
return 0;
}

static void __exit print_vma_exit(void) {
printk("good bye kernel...\n");
}

module_init(print_vma_init);
module_exit(print_vma_exit);
MODULE_LICENSE("GPL");

编写Makefile:

1
2
3
4
5
6
7
8
9
obj-m:=vma.o                          
CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
LINUX_KERNEL:=$(shell uname -r) #linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)

all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

运行sudo insmod vma.ko pid=1526命令,其中vma.ko通过make命令得到,1526是运行中的test进程的pid

image-20230718142455468-1689691400242-5

其实上述编写的可加载内核模块的作用等价于cat /proc/${pid}/maps,都是显示进程的内存布局情况。

image-20230718142553863-1689691400242-8