Binary Makeself

11 篇文章 0 订阅
8 篇文章 0 订阅

本文展示了Linux 系统下的二进制的Makeself 的制作:将数据和对数据的操作同时包含在一个巨大的可执行文件当中,也引出了C 代码中操作数据文件的技巧——不使用文件操作而直接使用指针来操作目标文件中的数据。


1. Makeself 是什么?

Makeself 是一类包含数据的shell 脚本,通常这些数据是一个归档文件,Makeself 不依赖任何额外的文件,自身就可以完成工作。

说道这里不知道会不会有人感到陌生,不过说两个例子你马上就会想起来「喔,我见过这种东西」:

名称类型实质
支付宝控件安装脚本.shshell 脚本
NVIDIA 驱动安装程序.runshell 脚本


下面以一个例子来具体的解释一下何为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


2. 制作一个二进制的makeself 程序

现在回到了我们的标题:如何制作一个「Binary Makeself」?

根据之前制作shell 脚本的方法来看,我们需要将数据包含到可执行文件当中,同时又不损坏可执行文件,完成这个工作后,我们就可以在可执行文件中操作这些数据了。

下面以C 语言为例展示了在64 位Linux 系统下的二进制的makeself 程序的制作,功能同之前制作的「makeself.sh」相同,运行后调用mplayer 播放「あなたがいた森」——即使你的电脑无法获取到这首歌。


2.1 转储数据文件

不知道你看到这里会不会觉得我们做的事情和一个东西很相像:静态链接。

刚学C 语言的时候往往都会接触到动态链接和静态链接的概念,很多的书中作者一边讲解着这个变革一边缅怀那个自身也没体验过的时代。

我们现在干的事情和静态编译有些类似:数据文件是无法在C 代码中操作的,C 代码能操作的极限就是有符号表(symbol table)的目标文件(object file)。那么第一个工作就是将数据文件转换成带符号表的目标文件。

「objcopy」,不知道你对这个指令是否熟悉,它可以在目标文件之间进行操作,理所当然也能生成目标文件。我们先用这条指令转储数据文件(楼主的环境是linux x64 所以生成了x64 的elf 格式,如果是x86 的自己改一下参数):

OK,现在一首mp3 的数据都被转储到了目标文件中。


2.2 在C 代码中直接使用指针操作数据文件

现在的步骤就容易多了,我们只需要在代码中操作这些数据,并最终生成一个(可能体积巨大的)可执行文件即可。

首先看看目标文件的符号表,记录下我们需要的符号名称:

这便是需要的符号,顾名思义,它们分别表示了数据的起始、终止和长度。

把这些符号打印出来以便在代码中使用:

之后只需要在代码中引用这些符号即可,就像下面一样:

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 <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;
  const 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);
    }

  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 = vfork ()) < 0)
    {
      perror ("vfork");
      exit (1);
    }
  else if (0 == pid)
    {
      execlp ("mplayer", "mplayer", DATA_FILE, NULL);
    }

  if (pid != waitpid (pid, NULL, 0))
    {
      perror ("waitpid");
    }
  clear ();

  return 0;
}

2.3 生成包含要操作的数据在内的可执行文件

最后,只需要编译成一个巨大的可执行文件即可,可执行文件中包含了所需要的数据和对数据的操作,这就是一个二进制版本的Makeself。可执行文件的体积视你插入的数据而定,但是一般来说它会比普通的程序大很多:

就这样,祝你愉快~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值