#!/usr/bin/env bash
set -e -o pipefail -u
set -x
# This command determines the version of Go used to build the specified
# executable. It works on linux/amd64 executables, and possibly other 64-bit
# little-endian architectures that use ELF. It works on executables built with
# go1.4 and later, since it depends on the presence of the
# runtime.buildVersion string.

NM=""
READELF=""

for tool in "nm" "gnm"; do
	if [[ -n "$NM" ]]; then
		break
	fi
	NM="$(which -- "$tool" || true)"
done
for tool in "readelf" "greadelf"; do
	if [[ -n "$READELF" ]]; then
		break
	fi
	READELF="$(which -- "$tool" || true)"
done

FILE="$1"

LANG="C"  # run OSX tools in binary mode
WORDLEN="8"  # 64-bit pointers in Go string headers

if [[ -z "$($NM -- "$FILE" | grep ' T runtime\.init$' | head -n 1)" ]]; then
	echo "not an ELF Go executable" >&2
	exit 1
fi

buildVersion="$($NM --print-size -- "$FILE" | grep ' D runtime\.buildVersion$' | head -n 1)"

if [[ -z "$buildVersion" ]]; then
	echo "runtime.buildVersion symbol not present" >&2
	echo "go1"
	exit 1
fi

parts=($buildVersion)
addr="$((16#${parts[0]}))"
len="$((16#${parts[1]}))"

if [[ "$len" != "$((2*$WORDLEN))" ]]; then
	echo "unexpected string struct length $len; is this a 64-bit executable?" >&2
	exit 1
fi

read_offset ()
{
	addr="$1"
	len="$2"

	while read line
	do
		parts=($line)
		if [[ "${#parts[@]}" != 10 ]]; then
			continue
		fi
		sectionAddress="$((16#${parts[2]}))"
		sectionOffset="$((16#${parts[3]}))"
		sectionSize="$((16#${parts[4]}))"

		if [[ "$sectionAddress" -gt "$addr" ]]; then
			continue
		fi
		if [[ "$(($sectionAddress+$sectionSize))" -lt "$(($addr+$len))" ]]; then
			continue
		fi

		head -c "$(($addr-$sectionAddress+$sectionOffset+$len))" -- "$FILE" | tail -c "$len"
	done < <($READELF --section-headers -- "$FILE" | grep -A 1 '[0-9]\] ' | awk '{print}' ORS=' ' | sed -e 's/--/\
/g' | sed -n -e 's/.*[0-9]\] //p')
}

rev ()
{
	# /usr/bin/rev
	 xxd -p | sed -e 's/../& /g' | tr ' ' '\n' | cat -n | sort -r -n | awk '{print $2}' | tr -d '\n' | xxd -r -p
}

# reverse little-endian bytes
stringHeader="$(read_offset "$addr" "$len" | rev | xxd -p)"
stringLen="$((16#${stringHeader:0:$((2*$WORDLEN))}))"
stringAddr="$((16#${stringHeader:$((2*$WORDLEN)):$((2*$WORDLEN))}))"

read_offset "$stringAddr" "$stringLen"
echo
