[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
ELF转二进制(3/4):动态加载和运行
By Falcon of TinyLab.org Dec 09, 2019
背景简介
有一天,某位同学在讨论群聊起来:
除了直接把 C 语言程序编译成 ELF 运行以外,是否可以转成二进制,然后通过第三方程序加载到内存后再运行。
带着这样的问题,我们写了四篇文章,这是其三。
前面两篇分别讨论了如何把一个转成 Binary 的 ELF 作为一个新的 Section 加入到另外一个程序中执行:
如何实现动态加载和运行
本文继续讨论,但是方向是,在运行时加载 Binary 并运行,支持前面两篇中的两种类型的 Binary:绝对数据地址、相对数据地址。
先加载数据位置无关的 Binary 文件
先来考虑最简单的相对数据地址,动态加载后,仅需考虑把加载的 Binary 所在内存范围设置可执行即可。
把文件加载到内存中并设置内存保护属性的最佳方式是 mmap,当然也可以用 malloc/memalgin 分配内存然后用 mprotect 设置内存保护属性,但是需要额外考虑对齐。
可以直接基于 man mmap
中的例子小改一番,先拿到这个原始的例子:
$ man mmap | sed -ne "/Program source/,/SEE ALSO/p" | egrep -v "Program|SEE" | sed -e "s/^ //g" > mmap.orig.c
做完如下修改,得到一个 mmap.new.c:
$ git diff mmap.orig.c mmap.new.c
diff --git a/mmap.orig.c b/mmap.new.c
index 640bcb0..fe039c8 100644
--- a/mmap.orig.c
+++ b/mmap.new.c
@@ -49,11 +49,12 @@ main(int argc, char *argv[])
length = sb.st_size - offset;
}
- addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
+ addr = mmap(NULL, length + offset - pa_offset, PROT_READ|PROT_EXEC,
MAP_PRIVATE, fd, pa_offset);
if (addr == MAP_FAILED)
handle_error("mmap");
+#if 0
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
if (s != length) {
if (s == -1)
@@ -62,6 +63,9 @@ main(int argc, char *argv[])
fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
+#else
+ ((void (*)(void))addr)();
+#endif
exit(EXIT_SUCCESS);
}
上面的改动很简单,一方面是调整内存保护属性为 MAP_EXEC,另外一方面是把映射完的随机地址转换为一个 void (*)void
函数,然后直接执行,也就是调用 Binary,同时把之前的打印到控制台的部分注释掉。
以 -m32 参数编译,确保可以跑 -m32 的代码(继承上面两节):
$ gcc -m32 -o mmap.new mmap.new.c
这个 mmap.new 即可运行第二篇得到的采用相对数据地址的 hello.bin:
$ ./mmap.new ./hello.bin 0
Hello World
再讨论数据位置固定的 Binary 文件
如果要带绝对地址的 hello.bin 呢?
由于数据加载地址是写死的,那意味着必须告知 mmap 映射到一个固定的地址,即 .text 的装载地址,否则数据访问就会出错。mmap 的第一个参数 addr 配合第三个参数 prot(设置为 MAP_FIXED),恰好可以做到。
只是,mmap 要求这个地址必须是对齐到页表的,这个 page size 可以通过 sysconf(_SC_PAGE_SIZE)
拿到。
可是,默认链接的时候,.text 段并不是对齐到 page size 的,对齐到 page size 的是 entry addr,还有一个 0x54 的偏移,即 elf header + program header。这个 0x54 末尾的地址不会是 page size 对齐的。
那意味者链接的时候得“做点手脚”,得强制让 .text 对齐到 page size,我们观察到 0x8046000 这个可以安全使用,因为程序都是从 0x8048000 之后的。当然,这里可用的只有不到 0x2000,8k,比这个大就把这个地址再改小吧。
怎么强制修改 .text 的装载地址呢,一个是上节提到的修改 ld.script,另外一个是直接用 ld 的 -Ttext 参数:
$ as --32 -o hello.o hello.s
$ ld -melf_i386 -o hello hello.o -Ttext=0x8046000
之后,我们再做一些修改,允许传递这个地址给 mmap,得到 mmap.any.c:
$ diff --git a/mmap.orig.c b/mmap.any.c
index 640bcb0..aa23eb2 100644
--- a/mmap.orig.c
+++ b/mmap.any.c
@@ -11,7 +11,7 @@
int
main(int argc, char *argv[])
{
- char *addr;
+ char *addr = NULL;
int fd;
struct stat sb;
off_t offset, pa_offset;
@@ -19,7 +19,7 @@ main(int argc, char *argv[])
ssize_t s;
if (argc < 3 || argc > 4) {
- fprintf(stderr, "%s file offset [length]\n", argv[0]);
+ fprintf(stderr, "%s file offset [addr]\n", argv[0]);
exit(EXIT_FAILURE);
}
@@ -40,20 +40,16 @@ main(int argc, char *argv[])
}
if (argc == 4) {
- length = atoi(argv[3]);
- if (offset + length > sb.st_size)
- length = sb.st_size - offset;
- /* Can't display bytes past end of file */
-
- } else { /* No length arg ==> display to end of file */
- length = sb.st_size - offset;
+ sscanf(argv[3], "%p", &addr);
}
+ length = sb.st_size - offset;
- addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
- MAP_PRIVATE, fd, pa_offset);
+ addr = mmap((void *)addr, length + offset - pa_offset, PROT_READ|PROT_EXEC,
+ MAP_PRIVATE|MAP_FIXED, fd, pa_offset);
if (addr == MAP_FAILED)
handle_error("mmap");
+#if 0
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
if (s != length) {
if (s == -1)
@@ -62,6 +58,9 @@ main(int argc, char *argv[])
fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
+#else
+ ((void (*)(void))addr)();
+#endif
exit(EXIT_SUCCESS);
}
这个改动把原来的参数 length 换掉,替换为 addr,允许直接通过第三个程序参数设置 .text 的装载地址(确保数据地址有效)。
重新编译 mmap.any.c 并运行:
$ gcc -m32 -o mmap.any mmap.any.c
$ ./mmap.any ./hello.bin 0 0x8046000
Hello World
需要注意的是,这个地址必须与 -Ttext 指定的地址一致。
小结
到这里,ELF转二进制 3 篇文章就完成了,分别讨论了静态嵌入、位置无关和动态加载,接下来还有一篇会讨论,如何动态修改数据地址。
欢迎订阅吴老师的 10 小时 C 语言进阶视频课:《360° 剖析 Linux ELF》,课程提供了超过 70 多份实验材料,其中 15 个例子演示了 15 种程序执行的方法。
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 ¥9.68元 | |
请作者喝杯咖啡吧 |