今天发现了一段很有意思的C程序(http://blog.chinaunix.net/uid-233938-id-162628.html),实现的功能就是自己打印自已的内容。
这其中的道理其实是在程序链接阶段做了手脚!先看程序如下:
test.c
#include <stdio.h>
#include <stdlib.h>
extern char * _binary_test_c_start;
int main()
{
printf("%s", (char *)&_binary_test_c_start);
}
Makefile内容为:
#Makefile
test: test.obj test.c
gcc -o test test.c test.obj
test.obj:test.c
objcopy --input-target=binary --binary-architecture=i386 --output-target=elf64-x86-64 test.c test.obj
clean:
@-rm test.obj test
这其中最重要的还是使用objcopy的技巧,没想到objcopy这么强大,可以把纯C文本转化成目标文件,那么文件内容就被插入最终生成的test.obj目标文件中。我们可以使用readelf看下生成的test.obj中的section:
$ readelf --section-headers --wide test.obj
There are 5 section headers, starting at offset 0x190:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .data PROGBITS 00000000 000034 0000a9 00 WA 0 0 1
[ 2] .symtab SYMTAB 00000000 0000e0 000050 10 3 2 4
[ 3] .strtab STRTAB 00000000 000130 00003d 00 0 0 1
[ 4] .shstrtab STRTAB 00000000 00016d 000021 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
仔细观察section .data的Size为0xa9,这正是test.c的文件大小。我们可以dump下.data的内容:
$ readelf --hex-dump=1 test.obj
Hex dump of section '.data':
0x00000000 23696e63 6c756465 203c7374 64696f2e #include <stdio.
0x00000010 683e0a23 696e636c 75646520 3c737464 h>.#include <std
0x00000020 6c69622e 683e0a20 0a657874 65726e20 lib.h>. .extern
0x00000030 63686172 205f6269 6e617279 5f746573 char _binary_tes
0x00000040 745f635f 73746172 743b0a69 6e74206d t_c_start;.int m
0x00000050 61696e28 696e7420 61726763 2c206368 ain(int argc, ch
0x00000060 6172202a 61726776 5b5d290a 7b0a0970 ar *argv[]).{..p
0x00000070 72696e74 66282225 73222c20 28636861 rintf("%s", (cha
0x00000080 72202a29 265f6269 6e617279 5f746573 r *)&_binary_tes
0x00000090 745f635f 73746172 74293b0a 09726574 t_c_start);..ret
0x000000a0 75726e20 303b0a7d 0a urn 0;.}.
可以看到section .data中存储的正是test.c的文件内容。
查看一下test.obj的符号表:
[22:36:50@astrol:/tmp/print]$ readelf --syms --wide test.obj
Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _binary_test_c_start
3: 000000a9 0 NOTYPE GLOBAL DEFAULT 1 _binary_test_c_end
4: 000000a9 0 NOTYPE GLOBAL DEFAULT ABS _binary_test_c_size
可以看到这其中的_binary_test_c_start正是test.c使用到的。
到这里可能还有童鞋问,为什么是_binary_test_c_start呢?这就得了解objcopy的-B选项了:
-B bfdarch
--binary-architecture=bfdarch
Useful when transforming a architecture-less input file into an object file. In this case the output architecture can be set to
bfdarch. This option will be ignored if the input file has a known bfdarch. You can access this binary data inside a program by
referencing the special symbols that are created by the conversion process. These symbols are called _binary_objfile_start,
_binary_objfile_end and _binary_objfile_size. e.g. you can transform a picture file into an object file and then access it in your
code using these symbols.
这下就一切都明了了吧,其中的objfile是变化的!
需要了解的一点是,符号表中Value是symbol的地址,而不是symbol所指代的变量的值。所以我们在程序中声明_binary_test_c_start为任何type都可以,只要最终通过取地址符&获取其地址就可以。
好了,本文就到这里。其实程序本身没什么价值,就是觉得好玩而已,但是我觉得熟悉这些工具才是最重要的,不是吗? 某种程度上就是增加了解决问题的途径!
参考链接:
《Embedding binary data in executables》
《Linux中使用C語言載入data Object 檔案資料》
《Linux中使用C語言載入data Object 檔案資料 (續)》
《Include binary file with GNU ld linker script》
《linking arbitrary data using GCC ARM toolchain》
《Not yet reviewed by OSv team. Might contain errors and inaccuracies.》
《How does the `--defsym` linker flag work to pass values to source code?》