本文展示了Linux 系统下的二进制的makeself 程序的制作:将数据和对数据的操作同时包含在一个巨大的可执行文件当中。
Makeself 是一类包含数据的shell 脚本,通常这些数据是一个归档文件,Makeself 脚本不依赖任何额外的文件,自身就可以完成工作。
说道这里不知道会不会有人感到陌生,不过说两个例子你马上就会想起来「喔,我见过这种东西」:
名称 | 类型 | 实质 |
支付宝控件 | shell 脚本 | shell 脚本 |
NVIDIA 驱动安装程序 | run 文件 | shell 脚本 |
下面以一个例子来具体的解释一下何为Makeself:
#!/usr/bin/env bash # filename: makeself.sh # A example of a makeself script by iSpeller DATA_FILE=/tmp/self-playing.data OFFSET=$(awk '/^__HERE_OLD_WORLD_END_AND_A_NEW_WORLD_BEGAN__/ {print NR + 1; exit 0; }' "$0") tail -n+$OFFSET "$0" > $DATA_FILE mplayer $DATA_FILE rm -f $DATA_FILE exit 0 # Your data must below next line __HERE_OLD_WORLD_END_AND_A_NEW_WORLD_BEGAN__
上面是一个简化的makeself 脚本,脚本最后一行设置了一个标号,而脚本所做的工作就是找到这个标号(使用awk),然后把标号之后的数据全部输出成一个文件(使用tail 重定向),之后就是对数据的处理了(这里我们使用的mplayer)。
既然如此,我们只要把多媒体文件追加到脚本的最后,脚本就可以调用mplayer 来播放,并按照你期望的运行:
当然这里我假定了你的系统上安装了mplayer ;p
现在回到了我们的标题:如何制作一个「Binary Makeself」?
根据之前制作shell 脚本的方法来看,我们需要将数据包含到可执行文件当中,同时又不损坏可执行文件,完成这个工作后,我们就可以在可执行文件中操作这些数据了。
下面以C 语言为例展示了在64 位Linux 系统下的二进制的makeself 程序的制作,功能同之前制作的「makeself.sh」相同,运行后调用mplayer 播放「あなたがいた森.mp3」——即使你的电脑无法获取到这首歌。
不知道你看到这里会不会觉得我们做的事情和一个东西很相像:静态链接。
刚学C 语言的时候往往都会接触到动态链接和静态链接的概念,很多的书中作者一边讲解着这个变革一边缅怀那个自身都没经历过的时代。
我们现在干的事情和静态编译有些类似:数据文件是无法在C 代码中操作的,C 代码能操作的极限就是有符号表(symbol table)的目标文件(object file)。那么第一个工作就是将数据文件转换成带符号表的目标文件。
「objcopy」,不知道你对这个指令是否熟悉,它可以在目标文件之间进行操作,理所当然也能生成目标文件。我们先用这条指令转储数据文件(楼主的环境是linux x64 所以生成了x64 的elf 格式,如果是x86 的自己改一下参数):
OK,现在一首mp3 的数据都被转储到了目标文件中。
现在的步骤就容易多了,我们只需要在代码中操作这些数据,并最终生成一个(可能体积巨大的)可执行文件即可。
首先看看目标文件的符号表,记录下我们需要的符号名称:
这便是需要的符号,顾名思义,它们分别表示了数据的起始、终止和长度。
把这些符号打印出来以便在代码中使用:
之后只需要在打码中引用这些符号即可,就像下面一样:
extern char _binary_______________________mp3_start[]; extern char _binary_______________________mp3_end[]; extern char _binary_______________________mp3_size[];最后是一段C 代码示例,功能同前面的「makeself.sh」一样,调用mplayer 来播放音乐数据文件。其中引用的符号需要换成你从符号表中得到的符号:
/* A program to show C-style makeself by iSpeller * Complie with gcc on Linux */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <sys/wait.h> const char * const DATA_FILE = "/tmp/self-playing.data"; /* See your object file's symbol table and replace next 3 array * We will not use all of them in this program */ extern char _binary_______________________mp3_start[]; extern char _binary_______________________mp3_end[]; extern char _binary_______________________mp3_size[]; void write_data (void) { int datafd; char *data = _binary_______________________mp3_start; uint64_t WRITE_LEN = (uint64_t)_binary_______________________mp3_size; if (0 == access (DATA_FILE, F_OK)) { if (-1 == unlink (DATA_FILE)) { exit (1); } } if ((datafd = open(DATA_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) { perror ("open"); exit (1); } else { if (-1 == write (datafd, data, WRITE_LEN)) { perror ("write"); } close (datafd); } } void clear (void) { if (0 == access (DATA_FILE, F_OK)) { if (-1 == remove (DATA_FILE)) { perror ("remove"); } } } int main (void) { pid_t pid; write_data (); if ((pid = fork ()) < 0) { perror ("fork"); exit (1); } else if (0 == pid) { execlp ("mplayer", "mplayer", DATA_FILE, NULL); } if (pid != waitpid (pid, NULL, 0)) { perror ("waitpid"); } clear (); return 0; }
最后,只需要编译成一个巨大的可执行文件即可,可执行文件中包含了所需要的数据和对数据的操作,这就是一个二进制版本的Makeself。可执行文件的体积视你插入的数据而定,但是一般来说它会比普通的程序大很多:
就这样,祝你愉快~
原文地址:http://blog.csdn.net/ispeller/article/details/45268511