标签:red 程序 需求 更新 set 方法 add 直接 开发
原创 吴章金 Linux阅码场 2019-11-18Section 是 Linux ELF 程序格式的一种核心数据表达方式,用来存放一个一个的代码块、数据块(包括控制信息块),这样一种模块化的设计为程序开发提供了很大的灵活性。
需要增加一个功能,增加一份代码或者增加一份数据都可以通过新增一个 Section 来实现。Section 的操作在 Linux 内核中有着非常广泛的应用,比如内核压缩,比如把 .config 打包后加到内核映像中。
下面介绍三种新增 Section 的方式:汇编、C 和 ELF 工具。
如何创建一个可执行的共享库 中有一个很好的例子:
asm(".pushsection .interp,\"a\"\n"
" .string \"/lib/i386-linux-gnu/ld-linux.so.2\"\n"
".popsection");
通过上述代码新增了一个 .interp Section,用于指定动态链接器。简单介绍一下这段内联汇编:
稍微延伸两点:
".ascii \"/lib/i386-linux-gnu/ld-linux.so.2\\x00\"\n"
.incbin 方式在 Linux 内核中用处相当广泛,例如:
本节完整演示代码如下:
#include <stdio.h>
#include <unistd.h>
#if 1
asm(".pushsection .interp,\"a\"\n"
" .ascii \"/lib/i386-linux-gnu/ld-linux.so.2\\x00\"\n"
".popsection");
/* .ascii above equals to .string \"/lib/i386-linux-gnu/ld-linux.so.2\"\n */
#else
asm(".pushsection .interp,\"a\"\n"
" .incbin \"interp.section.txt\"\n"
".popsection");
#endif
int main(void)
{
printf("hello\n");
return 0;
}
void _start(void)
{
int ret;
ret = main();
_exit(ret);
}
编译和执行:
$ gcc -m32 -shared -fpic -o libhello.so hello.c
$ ./libhello.so
hello
attribute
新增一个 Section上面的需求可以等价地用 gcc attribute 编译属性来指定:
const char interp[] __attribute__((section(".interp"))) = "/lib/i386-linux-gnu/ld-linux.so.2";
本节完整演示代码如下:
#include <stdio.h>
#include <unistd.h>
const char interp[] __attribute__((section(".interp"))) = "/lib/i386-linux-gnu/ld-linux.so.2";
int main(void)
{
printf("hello\n");
return 0;
}
void _start(void)
{
int ret;
ret = main();
_exit(ret);
}
编译和执行方法同上,不做重复介绍。
上面介绍了 C 和汇编层面的方法,再来介绍一个工具层面的方法。
objcopy 这个工具很强大,其中就包括新增 Section。
接下来先准备一个文件 interp.section.text,记得末尾加的 \0 字节:
$ echo -e -n "/lib/i386-linux-gnu/ld-linux.so.2\x00" > interp.section.txt
接着准备一个 hello.c,里头不指定任何 .interp:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("hello\n");
return 0;
}
void _start(void)
{
int ret;
ret = main();
_exit(ret);
}
不过需要注意的是,objcopy 不能直接在最终的可执行文件和共享库中加入一个 Section:
$ objcopy --add-section .interp=interp.section.txt --set-section-flags .interp=alloc,readonly libhello.so
objcopy:stTyWnxc: can‘t add section ‘.interp‘: File in wrong format
怎么办呢,需要先加入到 .o 中,再链接,类似这样:
$ gcc -m32 -shared -fpic -c -o hello.o hello.c
$ objcopy --add-section .interp=interp.section.txt --set-section-flags .interp=alloc,readonly hello.o
$ gcc -m32 -shared -fpic -o libhello.so hello.o
注意,必须加上 --set-section-flags 配置为 alloc,否则,程序头会不纳入该 Section,结果将是缺少 INTERP 程序头而无法执行。
需要补充的是,本文介绍的 .interp 是一个比较特殊的 Section,链接时能自动处理,如果是新增了一个全新的 Section 类型,那么得修改链接脚本,明确告知链接器需要把 Section 放到程序头的哪个 Segment。
以上三种新增 Section 的方式适合不同的需求:汇编语言、C 语言、链接阶段,基本能满足日常的开发需要。
再补充一种方式,举个例子,上面用到的动态链接器来自 libc6:i386 这个包:
$ dpkg -S /lib/i386-linux-gnu/ld-linux.so.2
libc6:i386: /lib/i386-linux-gnu/ld-linux.so.2
如果系统安装的是 libc6-i386 呢?
$ dpkg -S /lib32/ld-linux.so.2
libc6-i386: /lib32/ld-linux.so.2
两个包提供的动态链接器路径完全不一样,那就得替换掉动态编译器,要重新编译 C 或者汇编吗?
其实不需要重新编译,因为可以直接这样换掉:
$ objcopy --dump-section .interp=interp.txt libhello.so
$ sed -i -e "s%/lib/i386-linux-gnu/ld-linux.so.2%/lib32/ld-linux.so.2%g" interp.txt
$ objcopy --update-section .interp=interp.txt libhello.so
$ ./libhello.so
hello
上面几组指令先把 .interp Section 取出来存到 interp.txt 文件中,再替换掉其中的动态链接器路径,最后再把新文件的内容更新进共享库。
以上主要介绍了 Linux ELF 核心数据表达方式 Section 的多种 add 和 update 用法,掌握这些用户可以利于理解 Linux 内核源码中类似的代码,也可以用于实际开发和调试过程去解决类似的需求。
吴章金:通过操作 Section 为 Linux ELF 程序新增数据
标签:red 程序 需求 更新 set 方法 add 直接 开发
原文地址:https://blog.51cto.com/15015138/2555575