mirror of
https://github.com/Dasharo/romscope.git
synced 2026-03-06 15:02:51 -08:00
621 lines
13 KiB
Bash
Executable File
621 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# This script was generated by bashly 1.1.10 (https://bashly.dannyb.co)
|
|
# Modifying it manually is not recommended
|
|
|
|
# :wrapper.bash3_bouncer
|
|
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
|
|
printf "bash version 4 or higher is required\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# :command.master_script
|
|
|
|
# :command.version_command
|
|
version_command() {
|
|
echo "$version"
|
|
}
|
|
|
|
# :command.usage
|
|
cli_usage() {
|
|
if [[ -n $long_usage ]]; then
|
|
printf "cli - Sample application\n"
|
|
echo
|
|
|
|
else
|
|
printf "cli - Sample application\n"
|
|
echo
|
|
|
|
fi
|
|
|
|
printf "%s\n" "Usage:"
|
|
printf " cli COMMAND\n"
|
|
printf " cli [COMMAND] --help | -h\n"
|
|
printf " cli --version | -v\n"
|
|
echo
|
|
# :command.usage_commands
|
|
printf "%s\n" "Commands:"
|
|
printf " %s Extract subregions from a firmware binary\n" "extract"
|
|
printf " %s Compare two binaries\n" "compare"
|
|
echo
|
|
|
|
# :command.long_usage
|
|
if [[ -n $long_usage ]]; then
|
|
printf "%s\n" "Options:"
|
|
|
|
# :command.usage_fixed_flags
|
|
printf " %s\n" "--help, -h"
|
|
printf " Show this help\n"
|
|
echo
|
|
printf " %s\n" "--version, -v"
|
|
printf " Show version number\n"
|
|
echo
|
|
|
|
fi
|
|
}
|
|
|
|
# :command.usage
|
|
cli_extract_usage() {
|
|
if [[ -n $long_usage ]]; then
|
|
printf "cli extract - Extract subregions from a firmware binary\n"
|
|
echo
|
|
|
|
else
|
|
printf "cli extract - Extract subregions from a firmware binary\n"
|
|
echo
|
|
|
|
fi
|
|
|
|
printf "Alias: e\n"
|
|
echo
|
|
|
|
printf "%s\n" "Usage:"
|
|
printf " cli extract ROM [OUTPUT]\n"
|
|
printf " cli extract --help | -h\n"
|
|
echo
|
|
|
|
# :command.long_usage
|
|
if [[ -n $long_usage ]]; then
|
|
printf "%s\n" "Options:"
|
|
|
|
# :command.usage_fixed_flags
|
|
printf " %s\n" "--help, -h"
|
|
printf " Show this help\n"
|
|
echo
|
|
|
|
# :command.usage_args
|
|
printf "%s\n" "Arguments:"
|
|
|
|
# :argument.usage
|
|
printf " %s\n" "ROM"
|
|
printf " Path to coreboot binary\n"
|
|
echo
|
|
|
|
# :argument.usage
|
|
printf " %s\n" "OUTPUT"
|
|
printf " Directory to extract to (default: [romname].extracted)\n"
|
|
echo
|
|
|
|
# :command.usage_examples
|
|
printf "%s\n" "Examples:"
|
|
printf " cli extract coreboot.rom\n"
|
|
echo
|
|
|
|
fi
|
|
}
|
|
|
|
# :command.usage
|
|
cli_compare_usage() {
|
|
if [[ -n $long_usage ]]; then
|
|
printf "cli compare - Compare two binaries\n"
|
|
echo
|
|
|
|
else
|
|
printf "cli compare - Compare two binaries\n"
|
|
echo
|
|
|
|
fi
|
|
|
|
printf "Alias: c\n"
|
|
echo
|
|
|
|
printf "%s\n" "Usage:"
|
|
printf " cli compare ROM1 ROM2\n"
|
|
printf " cli compare --help | -h\n"
|
|
echo
|
|
|
|
# :command.long_usage
|
|
if [[ -n $long_usage ]]; then
|
|
printf "%s\n" "Options:"
|
|
|
|
# :command.usage_fixed_flags
|
|
printf " %s\n" "--help, -h"
|
|
printf " Show this help\n"
|
|
echo
|
|
|
|
# :command.usage_args
|
|
printf "%s\n" "Arguments:"
|
|
|
|
# :argument.usage
|
|
printf " %s\n" "ROM1"
|
|
printf " First ROM to compare\n"
|
|
echo
|
|
|
|
# :argument.usage
|
|
printf " %s\n" "ROM2"
|
|
printf " Second ROM to compare\n"
|
|
echo
|
|
|
|
fi
|
|
}
|
|
|
|
# :command.normalize_input
|
|
# :command.normalize_input_function
|
|
normalize_input() {
|
|
local arg flags passthru
|
|
passthru=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
arg="$1"
|
|
if [[ $passthru == true ]]; then
|
|
input+=("$arg")
|
|
elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
|
|
input+=("${BASH_REMATCH[1]}")
|
|
input+=("${BASH_REMATCH[2]}")
|
|
elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
|
|
input+=("${BASH_REMATCH[1]}")
|
|
input+=("${BASH_REMATCH[2]}")
|
|
elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then
|
|
flags="${BASH_REMATCH[1]}"
|
|
for ((i = 0; i < ${#flags}; i++)); do
|
|
input+=("-${flags:i:1}")
|
|
done
|
|
elif [[ "$arg" == "--" ]]; then
|
|
passthru=true
|
|
input+=("$arg")
|
|
else
|
|
input+=("$arg")
|
|
fi
|
|
|
|
shift
|
|
done
|
|
}
|
|
|
|
# :command.inspect_args
|
|
inspect_args() {
|
|
if ((${#args[@]})); then
|
|
readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort)
|
|
echo args:
|
|
for k in "${sorted_keys[@]}"; do
|
|
echo "- \${args[$k]} = ${args[$k]}"
|
|
done
|
|
else
|
|
echo args: none
|
|
fi
|
|
|
|
if ((${#other_args[@]})); then
|
|
echo
|
|
echo other_args:
|
|
echo "- \${other_args[*]} = ${other_args[*]}"
|
|
for i in "${!other_args[@]}"; do
|
|
echo "- \${other_args[$i]} = ${other_args[$i]}"
|
|
done
|
|
fi
|
|
|
|
if ((${#deps[@]})); then
|
|
readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort)
|
|
echo
|
|
echo deps:
|
|
for k in "${sorted_keys[@]}"; do
|
|
echo "- \${deps[$k]} = ${deps[$k]}"
|
|
done
|
|
fi
|
|
|
|
if ((${#env_var_names[@]})); then
|
|
readarray -t sorted_names < <(printf '%s\n' "${env_var_names[@]}" | sort)
|
|
echo
|
|
echo "environment variables:"
|
|
for k in "${sorted_names[@]}"; do
|
|
echo "- \$$k = ${!k:-}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# :command.user_lib
|
|
# src/lib/extract.sh
|
|
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
|
is_ifd_rom() {
|
|
filetype=$(file -b $1)
|
|
[[ $filetype = "Intel serial flash for PCH ROM" ]]
|
|
}
|
|
|
|
is_fmap_rom() {
|
|
grep -obUaP "__FMAP__" $1 &> /dev/null
|
|
[[ $? = 0 ]]
|
|
}
|
|
|
|
is_cbfs_rom() {
|
|
cbfstool $1 print &> /dev/null
|
|
[[ $? = 0 ]]
|
|
}
|
|
|
|
rom_extract() {
|
|
set +e # bleh
|
|
|
|
rom_file=$1
|
|
output_dir=$2
|
|
if [ ! -f $rom_file ]; then
|
|
echo "File $rom_file does not exist. Please input a valid ROM file path."
|
|
return 1
|
|
fi
|
|
if [ -z "$output_dir" ]; then
|
|
output_dir=$rom_file.extracted
|
|
fi
|
|
mkdir -p $output_dir
|
|
if is_ifd_rom $rom_file; then
|
|
echo "ROM contains an Intel Flash Descriptor"
|
|
mkdir -p $output_dir/regions/ifd
|
|
pushd $output_dir/regions/ifd > /dev/null
|
|
ifdtool -x $rom_file &> /dev/null
|
|
ls | fmt -s | sed "s/^/[IFD] /"
|
|
popd > /dev/null
|
|
echo
|
|
fi
|
|
if is_fmap_rom $rom_file; then
|
|
echo "ROM contains a coreboot FMAP"
|
|
regions=$(cbfstool $rom_file layout -w \
|
|
| grep \' \
|
|
| cut -d \' -f 2 \
|
|
| grep -v -e "SI_ALL" -e "SI_BIOS" -e "RW_SECTION_A" -e "RW_SECTION_B" -e "RW_MISC" -e "UNIFIED_MRC_CACHE" -e "RW_SHARED" -e "WP_RO" -e "RO_SECTION"
|
|
)
|
|
mkdir -p $output_dir/regions/fmap
|
|
pushd $output_dir/regions/fmap > /dev/null
|
|
for region in $regions; do
|
|
echo "[FMAP] $region.bin"
|
|
cbfstool $rom_file read -r $region -f $region.bin
|
|
if is_cbfs_rom $region.bin; then
|
|
echo "Region contains a CBFS"
|
|
mkdir -p $region.cbfs
|
|
cbfs_files=$(cbfstool $rom_file print -r $region | cut -d ' ' -f 1 | tail -n +3 | grep -v "(empty)")
|
|
pushd $region.cbfs > /dev/null
|
|
for file in $cbfs_files; do
|
|
echo " [CBFS] $file"
|
|
cbfstool $rom_file extract -r $region -f $(echo $file | tr \/ -) -n $file -m x86 &> /dev/null # TODO FIXME etc
|
|
done
|
|
popd > /dev/null
|
|
fi
|
|
done
|
|
popd > /dev/null
|
|
echo
|
|
fi
|
|
}
|
|
|
|
# :command.command_functions
|
|
# :command.function
|
|
cli_extract_command() {
|
|
# src/extract_command.sh
|
|
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
ROM_FILE=$(realpath "${args[rom]}")
|
|
OUTPUT_DIR="${args[output]}"
|
|
|
|
rom_extract $ROM_FILE $OUTPUT_DIR
|
|
|
|
}
|
|
|
|
# :command.function
|
|
cli_compare_command() {
|
|
# src/compare_command.sh
|
|
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
ROM1_FILE=$(realpath "${args[rom1]}")
|
|
ROM2_FILE=$(realpath "${args[rom2]}")
|
|
OUTPUT_DIR="${args[output]}"
|
|
|
|
WORKDIR=/tmp/romscope
|
|
|
|
main () {
|
|
if [ -e $WORKDIR ]; then
|
|
rm -r $WORKDIR
|
|
fi
|
|
|
|
echo "Extracting file $ROM1_FILE"
|
|
mkdir -p $WORKDIR/a
|
|
rom_extract $ROM1_FILE $WORKDIR/a > /dev/null
|
|
pushd $WORKDIR/a > /dev/null
|
|
find regions -type f -exec printf '%s\n' {} + > manifest
|
|
popd > /dev/null
|
|
|
|
echo "Extracting file $ROM2_FILE"
|
|
mkdir -p $WORKDIR/b
|
|
rom_extract $ROM2_FILE $WORKDIR/b > /dev/null
|
|
pushd $WORKDIR/b > /dev/null
|
|
find regions -type f -exec printf '%s\n' {} + > manifest
|
|
popd > /dev/null
|
|
|
|
pushd $WORKDIR > /dev/null
|
|
files=$(cat a/manifest \
|
|
| grep -v -e "regions/fmap/FW_MAIN_A.bin" -e "regions/fmap/FW_MAIN_B.bin" -e "regions/fmap/COREBOOT.bin" -e "regions/fmap/VBLOCK_A.bin" -e "regions/fmap/VBLOCK_B.bin" -e "regions/ifd/flashregion" \
|
|
)
|
|
for file in $files; do
|
|
echo "Generating report for $file"
|
|
diffoscope a/$file b/$file --html $(echo $file | tr \/ -).html &> /dev/null
|
|
done
|
|
popd > /dev/null
|
|
|
|
if [ -z "$OUTPUT_DIR" ]; then
|
|
OUTPUT_DIR="report"
|
|
fi
|
|
mkdir -p $OUTPUT_DIR
|
|
cp $WORKDIR/*.html $OUTPUT_DIR/
|
|
echo "Report placed in folder: '$OUTPUT_DIR'"
|
|
|
|
rm -r $WORKDIR
|
|
}
|
|
|
|
main
|
|
|
|
}
|
|
|
|
# :command.parse_requirements
|
|
parse_requirements() {
|
|
# :command.fixed_flags_filter
|
|
while [[ $# -gt 0 ]]; do
|
|
case "${1:-}" in
|
|
--version | -v)
|
|
version_command
|
|
exit
|
|
;;
|
|
|
|
--help | -h)
|
|
long_usage=yes
|
|
cli_usage
|
|
exit
|
|
;;
|
|
|
|
*)
|
|
break
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
# :command.dependencies_filter
|
|
if command -v cbfstool >/dev/null 2>&1; then
|
|
deps['cbfstool']="$(command -v cbfstool | head -n1)"
|
|
else
|
|
printf "missing dependency: cbfstool\n" >&2
|
|
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://review.coreboot.org/coreboot.git\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v ifdtool >/dev/null 2>&1; then
|
|
deps['ifdtool']="$(command -v ifdtool | head -n1)"
|
|
else
|
|
printf "missing dependency: ifdtool\n" >&2
|
|
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://review.coreboot.org/coreboot.git\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v ifdtool >/dev/null 2>&1; then
|
|
deps['diffoscope']="$(command -v ifdtool | head -n1)"
|
|
else
|
|
printf "missing dependency: diffoscope\n" >&2
|
|
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://diffoscope.org/\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# :command.command_filter
|
|
action=${1:-}
|
|
|
|
case $action in
|
|
-*) ;;
|
|
|
|
extract | e)
|
|
action="extract"
|
|
shift
|
|
cli_extract_parse_requirements "$@"
|
|
shift $#
|
|
;;
|
|
|
|
compare | c)
|
|
action="compare"
|
|
shift
|
|
cli_compare_parse_requirements "$@"
|
|
shift $#
|
|
;;
|
|
|
|
# :command.command_fallback
|
|
"")
|
|
cli_usage >&2
|
|
exit 1
|
|
;;
|
|
|
|
*)
|
|
printf "invalid command: %s\n" "$action" >&2
|
|
exit 1
|
|
;;
|
|
|
|
esac
|
|
|
|
# :command.parse_requirements_while
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
case "$key" in
|
|
|
|
-?*)
|
|
printf "invalid option: %s\n" "$key" >&2
|
|
exit 1
|
|
;;
|
|
|
|
*)
|
|
# :command.parse_requirements_case
|
|
# :command.parse_requirements_case_simple
|
|
printf "invalid argument: %s\n" "$key" >&2
|
|
exit 1
|
|
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
}
|
|
|
|
# :command.parse_requirements
|
|
cli_extract_parse_requirements() {
|
|
# :command.fixed_flags_filter
|
|
while [[ $# -gt 0 ]]; do
|
|
case "${1:-}" in
|
|
--help | -h)
|
|
long_usage=yes
|
|
cli_extract_usage
|
|
exit
|
|
;;
|
|
|
|
*)
|
|
break
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
# :command.command_filter
|
|
action="extract"
|
|
|
|
# :command.parse_requirements_while
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
case "$key" in
|
|
|
|
-?*)
|
|
printf "invalid option: %s\n" "$key" >&2
|
|
exit 1
|
|
;;
|
|
|
|
*)
|
|
# :command.parse_requirements_case
|
|
# :command.parse_requirements_case_simple
|
|
# :argument.case
|
|
if [[ -z ${args['rom']+x} ]]; then
|
|
args['rom']=$1
|
|
shift
|
|
# :argument.case
|
|
elif [[ -z ${args['output']+x} ]]; then
|
|
args['output']=$1
|
|
shift
|
|
else
|
|
printf "invalid argument: %s\n" "$key" >&2
|
|
exit 1
|
|
fi
|
|
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
# :command.required_args_filter
|
|
if [[ -z ${args['rom']+x} ]]; then
|
|
printf "missing required argument: ROM\nusage: cli extract ROM [OUTPUT]\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
}
|
|
|
|
# :command.parse_requirements
|
|
cli_compare_parse_requirements() {
|
|
# :command.fixed_flags_filter
|
|
while [[ $# -gt 0 ]]; do
|
|
case "${1:-}" in
|
|
--help | -h)
|
|
long_usage=yes
|
|
cli_compare_usage
|
|
exit
|
|
;;
|
|
|
|
*)
|
|
break
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
# :command.command_filter
|
|
action="compare"
|
|
|
|
# :command.parse_requirements_while
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
case "$key" in
|
|
|
|
-?*)
|
|
printf "invalid option: %s\n" "$key" >&2
|
|
exit 1
|
|
;;
|
|
|
|
*)
|
|
# :command.parse_requirements_case
|
|
# :command.parse_requirements_case_simple
|
|
# :argument.case
|
|
if [[ -z ${args['rom1']+x} ]]; then
|
|
args['rom1']=$1
|
|
shift
|
|
# :argument.case
|
|
elif [[ -z ${args['rom2']+x} ]]; then
|
|
args['rom2']=$1
|
|
shift
|
|
else
|
|
printf "invalid argument: %s\n" "$key" >&2
|
|
exit 1
|
|
fi
|
|
|
|
;;
|
|
|
|
esac
|
|
done
|
|
|
|
# :command.required_args_filter
|
|
if [[ -z ${args['rom1']+x} ]]; then
|
|
printf "missing required argument: ROM1\nusage: cli compare ROM1 ROM2\n" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z ${args['rom2']+x} ]]; then
|
|
printf "missing required argument: ROM2\nusage: cli compare ROM1 ROM2\n" >&2
|
|
exit 1
|
|
fi
|
|
|
|
}
|
|
|
|
# :command.initialize
|
|
initialize() {
|
|
version="0.1.0"
|
|
long_usage=''
|
|
set -e
|
|
|
|
}
|
|
|
|
# :command.run
|
|
run() {
|
|
declare -A args=()
|
|
declare -A deps=()
|
|
declare -a other_args=()
|
|
declare -a env_var_names=()
|
|
declare -a input=()
|
|
normalize_input "$@"
|
|
parse_requirements "${input[@]}"
|
|
|
|
case "$action" in
|
|
"extract") cli_extract_command ;;
|
|
"compare") cli_compare_command ;;
|
|
esac
|
|
}
|
|
|
|
initialize
|
|
run "$@"
|