From 8bb162afde3dbd8854eeac0b0528abae428bb0f7 Mon Sep 17 00:00:00 2001 From: oufme Date: Mon, 20 May 2024 11:57:31 -0700 Subject: [PATCH] Add unpack mode --- README.md | 67 ++++++++++++++++++--- packelf.sh | 174 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 201 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index f5a9e57..1c36607 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,69 @@ -`packelf` was inspired by [the idea of Klaus D](https://askubuntu.com/a/546305). It is used to package the elf executable and its dependent libraries into a single executable. - +`packelf` was inspired by [the idea of Klaus D](https://askubuntu.com/a/546305). It is used to pack a ELF program and its dependent libraries into a single executable file. ## usage ``` -packelf.sh [ADDITIONAL_LIBS ...] +Usage: ./packelf.sh [-zjJn] [ADDITIONAL_LIBS ...] + -zjJ compress flag passed to tar, '-z' by default + -n pack without compress ``` - - -## example +First, pack a ELF program. For example, you can pack `ls` like this: ``` -~ # packelf.sh `which perf` /root/perf -~ # /root/perf --version -perf version 3.10.0-1160.49.1.el7.x86_64.debug +# ./packelf.sh /bin/ls /root/ls +tar: Removing leading `/' from member names +'/bin/ls' was packed to '/root/ls' +Just run '/root/ls ARGS...' to execute the command. +Or run 'PACKELF_UNPACK_DIR=xxx /root/ls' to unpack it only. ``` +You can execute the packed program directly: + +``` +# /root/ls -lh /root/ls +-rwxr-xr-x 1 root root 1.3M May 21 08:35 /root/ls +``` + +However, every time the packed program is executed, an internal unpacking operation is performed automatically, which results in a slower startup of the program. + +If you need to execute the program many times and want to reduce the startup time, you can unpack the program before executing it. + +``` +# Run the packed program directly, it takes longer. +~ # time bash -c 'for i in {1..100};do /root/ls >/dev/null; done' +real 0m4.203s +user 0m2.067s +sys 0m3.093s + +# You can unpack it first. +~ # PACKELF_UNPACK_DIR=/usr/local/bin /root/ls +'ls' was unpacked to '/usr/local/bin'. +You can run '/usr/local/bin/ls ARGS...' to execute the command. + +# ls and ls.res are generated after unpacking. +~ # /usr/local/bin/ls -lh /usr/local/bin/ls* +-rwxr-xr-x 1 root root 3.9K May 21 09:00 /usr/local/bin/ls +/usr/local/bin/ls.res: +total 3.0M +-rwxr-xr-x 1 root root 175K May 21 09:00 ld-linux-x86-64.so.2 +-rwxr-xr-x 1 root root 2.0M May 3 2022 libc.so.6 +-rw-r--r-- 1 root root 15K May 3 2022 libdl.so.2 +-rw-r--r-- 1 root root 454K Feb 3 2018 libpcre.so.3 +-rwxr-xr-x 1 root root 142K May 3 2022 libpthread.so.0 +-rw-r--r-- 1 root root 152K Mar 1 2018 libselinux.so.1 +-rwxr-xr-x 1 root root 131K Jan 18 2018 ls + +# Running the unpacked launch script (/usr/local/bin/ls) will take much less time. +~ # time bash -c 'for i in {1..100};do /usr/local/bin/ls >/dev/null; done' +real 0m0.370s +user 0m0.239s +sys 0m0.133s +``` + +## dependence +* sh +* tar + +Note: If your tar doesn't support gzip, '-n' is needed when you pack a program. diff --git a/packelf.sh b/packelf.sh index f5ab6de..a7b04d0 100755 --- a/packelf.sh +++ b/packelf.sh @@ -1,36 +1,148 @@ -#!/bin/bash -set -eo pipefail +#!/usr/bin/env sh +#set -o pipefail # bash extension +set -e -[ $# -lt 2 ] && { - echo "usage: $0 [ADDITIONAL_LIBS ...]" - exit 1 +# These vars will be modified automatically with sed +run_mode=packer +compress_flag=-z +program= +ld_so= + +load() { + script_path="$0" + + while link_path=$(readlink "$script_path"); do + script_path="$link_path" + done + + unpack_dir=$(dirname "$script_path") + + exec "$unpack_dir/$program.res/$ld_so" \ + --library-path "$unpack_dir/$program.res" \ + "$unpack_dir/$program.res/$program" "$@" + # unreachable } -src="$1" -dst="$2" -shift -shift - -libs="$(ldd "$src" | grep -F '/' | sed -E 's|[^/]*/([^ ]+).*?|/\1|')" -ld_so="$(echo "$libs" | grep -F '/ld-linux-')" -ld_so="$(basename "$ld_so")" -program="$(basename "$src")" - -cat >"$dst" <>"$dst" 2> >(grep -v 'Removing leading' >&2) -chmod +x "$dst" +pack_help() { + echo "Usage: $0 [-zjJn] [ADDITIONAL_LIBS ...]" + echo " -zjJ compress flag passed to tar, '-z' by default" + echo " -n pack without compress" +} + +pack() { + [ $# -ge 2 ] || { + pack_help + exit 1 + } + + case $1 in + -z|-j|-J) + compress_flag=$1 + shift + ;; + -n) + compress_flag='' + shift + ;; + -h|--help) + pack_help + exit 0 + ;; + *) + ;; + esac + + src="$1" + shift + dst="$1" + shift + + libs="$(ldd "$src" | grep -F '/' | sed -E 's|[^/]*/([^ ]+).*?|/\1|')" + ld_so="$(echo "$libs" | grep -F '/ld-linux-')" + ld_so="$(basename "$ld_so")" + program="$(basename "$src")" + + cat "$0" | sed -E \ + -e 's/^run_mode=[^ ]*$/run_mode=unpacker/' \ + -e 's/^compress_flag=[^ ]*$/compress_flag='"$compress_flag"'/' \ + -e 's/^program=[^ ]*$/program='"$program"'/' \ + -e 's/^ld_so=[^ ]*$/ld_so='"$ld_so"'/' \ + >"$dst" + + tar $compress_flag -ch \ + --transform 's/.*\//'"$program"'.res\//' \ + "$src" $libs "$@" \ + >>"$dst" #\ + #2> >(grep -v 'Removing leading' >&2) # bash extension + + chmod +x "$dst" + echo "'$src' was packed to '$dst'" + echo "$dst" | grep -q / || dst="./$dst" + echo "Just run '$dst ARGS...' to execute the command." + echo "Or run 'PACKELF_UNPACK_DIR=xxx $dst' to unpack it only." +} + +unpack() { + if [ -n "$PACKELF_UNPACK_DIR" ]; then + [ -d "$PACKELF_UNPACK_DIR" ] || { + echo "'$PACKELF_UNPACK_DIR' is not a dir." + exit 1 + } + [ -e "$PACKELF_UNPACK_DIR/$program" ] && { + echo "'$PACKELF_UNPACK_DIR/$program' already exists, please remove it first." + exit 1 + } + unpack_dir="$PACKELF_UNPACK_DIR" + + else + if [ -n "$PACKELF_TMP_DIR" ]; then + unpack_dir="$PACKELF_TMP_DIR" + else + tmp_parent=/tmp/packelf_tmp + mkdir -p "$tmp_parent" + unpack_dir=$(mktemp -d -p "$tmp_parent" || echo "$tmp_parent") + fi + + trap 'rm -rf "$unpack_dir"' 0 1 2 3 6 10 12 13 14 15 + fi + + check_path="$unpack_dir/__check_permission__" + if ! (echo > "$check_path" && chmod +x "$check_path" && [ -x "$check_path" ]); then + rm -rf "$unpack_dir" + tmp_parent="$(pwd)/packelf_tmp" + mkdir -p "$tmp_parent" + unpack_dir="$(mktemp -d -p "$tmp_parent" || echo "$tmp_parent")" + fi + + sed '1,/^#__END__$/d' "$0" | tar $compress_flag -x -C "$unpack_dir" + sed -i 's@/etc/ld.so.preload@/etc/___so.preload@g' "$unpack_dir/$program.res/$ld_so" + + if [ -n "$PACKELF_UNPACK_DIR" ]; then + sed '/^#__END__$/,$d' "$0" > "$unpack_dir/$program" + sed -i -E 's/^run_mode=\w*$/run_mode=loader/' "$unpack_dir/$program" + chmod +x "$unpack_dir/$program" + echo "'$program' was unpacked to '$unpack_dir'." + echo "$unpack_dir" | grep -q / || unpack_dir="./$unpack_dir" + echo "You can run '$unpack_dir/$program ARGS...' to execute the command." + + else + "$unpack_dir/$program.res/$ld_so" \ + --library-path "$unpack_dir/$program.res" \ + "$unpack_dir/$program.res/$program" "$@" + exit $? + fi +} + + +if [ "$run_mode" = unpacker ]; then + unpack "$@" +else + pack "$@" +fi + +exit 0 +#__END__