Add unpack mode
This commit is contained in:
parent
e816bafc35
commit
8bb162afde
67
README.md
67
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 <ELF_SRC_PATH> <DST_PATH> [ADDITIONAL_LIBS ...]
|
||||
Usage: ./packelf.sh [-zjJn] <ELF_SRC_PATH> <DST_PATH> [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.
|
||||
|
|
174
packelf.sh
174
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 <ELF_SRC_PATH> <DST_PATH> [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" <<EOF
|
||||
#!/bin/bash
|
||||
tmp_dir="\$(mktemp -d)"
|
||||
check_path="\$tmp_dir/__check_permission__"
|
||||
trap 'rm -rf \$tmp_dir' 0 1 2 3 6
|
||||
if ! (touch "\$check_path" && chmod +x "\$check_path" && [ -x "\$check_path" ]); then
|
||||
rm -rf "\$tmp_dir"
|
||||
tmp_dir="\$(TMPDIR="\$(pwd)" mktemp -d)"
|
||||
if [ "$run_mode" = loader ]; then
|
||||
load "$@"
|
||||
fi
|
||||
sed '1,/^#__END__$/d' "\$0" | tar -xz -C "\$tmp_dir"
|
||||
sed -i 's@/etc/ld.so.preload@/etc/___so.preload@g' "\$tmp_dir/$ld_so"
|
||||
"\$tmp_dir/$ld_so" --library-path "\$tmp_dir" "\$tmp_dir/$program" "\$@"
|
||||
exit \$?
|
||||
#__END__
|
||||
EOF
|
||||
|
||||
tar -czh --transform 's/.*\///g' "$src" $libs "$@" >>"$dst" 2> >(grep -v 'Removing leading' >&2)
|
||||
chmod +x "$dst"
|
||||
pack_help() {
|
||||
echo "Usage: $0 [-zjJn] <ELF_SRC_PATH> <DST_PATH> [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__
|
||||
|
|
Loading…
Reference in New Issue
Block a user