blob: cdbdddae8b02759969c1fc7723e2fd52458d31e1 [file] [log] [blame]
#!/usr/bin/env bash
# Copyright 2016 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT
# This script reads symbols with nm and writes a C header file that
# defines macros <NAME>_CODE_*, <NAME>_DATA_* and <NAME>_ENTRY, with
# the address constants found in the symbol table for the symbols
# CODE_*, DATA_* and _start, respectively.
#
# When there is a dynamic symbol table, then it also emits macros
# <NAME>_DYNSYM_* giving the dynamic symbol table index of each
# exported symbol, and <NAME>_DYNSYM_COUNT giving the total number
# of entries in the table.
usage() {
echo >&2 "\
Usage: $0 NM READELF [--depfile DEPFILE] [--writable] OUTFILE {NAME DSO}..."
exit 2
}
if [ $# -lt 3 ]; then
usage
fi
NM="$1"
shift
READELF="$1"
shift
DEPFILE=
if [ $# -gt 2 -a "$1" = "--depfile" ]; then
DEPFILE="$2"
shift 2
fi
USED_FILES=()
OUTFILE="$1"
shift
set -e
if [ -n "$BASH_VERSION" ]; then
set -o pipefail
fi
grok_code_symbols() {
local symbol type addr size rest
while read symbol type addr size rest; do
case "$symbol" in
CODE_*|DATA_*|SYSCALL_*|_start)
if [ "$symbol" = _start ]; then
symbol=ENTRY
fi
echo "#define ${1}_${symbol} 0x${addr}" >> $OUTFILE
case "$size" in
''|0|0x0) ;;
*) echo "#define ${1}_${symbol}_SIZE 0x${size}" >> $OUTFILE
esac
status=0
;;
esac
done
return $status
}
find_code_symbols() {
"$NM" -P -S -n "$2" | grok_code_symbols "$1"
}
grok_dynsym_slots() {
local symno=0
local symbol rest
while read symbol rest; do
symno=$((symno+1))
echo "#define ${1}_DYNSYM_${symbol} ${symno}" >> $OUTFILE
done
if [ $symno -gt 0 ]; then
symno=$((symno+1))
echo "#define ${1}_DYNSYM_COUNT ${symno}" >> $OUTFILE
fi
}
find_dynsym_slots() {
"$NM" -P -D -p "$2" | grok_dynsym_slots "$1"
}
SEGMENTS_HAVE_DYNSYM=22
SEGMENTS_NO_DYNSYM=33
grok_segments() {
local line
local status=$SEGMENTS_NO_DYNSYM
while read line; do
case "$line" in
# Program header for the code segment, e.g.:
# LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x00f50f 0x00f50f R E 0x1000
# ^^^^^ vaddr ^^^^^^ ^filesz^
*LOAD*\ R\ E\ *)
local words=($line)
local vaddr=$(printf '%#x' ${words[2]})
local filesz=$(printf '%#x' ${words[4]})
echo "#define ${1}_CODE_START $vaddr" >> $OUTFILE
echo "#define ${1}_CODE_END (((${1}_CODE_START + $filesz + (1 << PAGE_SIZE_SHIFT) - 1) >> PAGE_SIZE_SHIFT) << PAGE_SIZE_SHIFT)" >> $OUTFILE
;;
# Make sure there's no writable segment unless --writable allows it.
*LOAD*W*)
$WRITABLE || {
echo >&2 "$0: writable segment: $line"
exit 1
}
echo "#define ${1}_DATA_START $vaddr" >> $OUTFILE
echo "#define ${1}_DATA_END (((${1}_DATA_START + $filesz + (1 << PAGE_SIZE_SHIFT) - 1) >> PAGE_SIZE_SHIFT) << PAGE_SIZE_SHIFT)" >> $OUTFILE
;;
# Section header for .dynsym, e.g.:
# [ 3] .dynsym DYNSYM 0000000000001268 001268 000018 18 A 6 1 8
# ^^^^^ addr ^^^^^ ^size^ ^entsize^
*DYNSYM*)
line="${line#*DYNSYM}"
local words=($line)
local addr=$(printf '%#x' 0x${words[0]})
local size=$(printf '%#x' 0x${words[2]})
local entsize=$(printf '%#x' 0x${words[3]})
# A stub .dynsym has a single entry. Don't count that case.
if [ $size != $entsize ]; then
status=$SEGMENTS_HAVE_DYNSYM
echo "#define ${1}_DATA_START_dynsym $addr" >> $OUTFILE
echo "#define ${1}_DATA_END_dynsym (${1}_DATA_START_dynsym + $size)" >> $OUTFILE
fi
;;
# Hex dump of section '.note.gnu.build-id':
# 0x000001c8 04000000 08000000 03000000 474e5500 ............GNU.
*Hex\ dump*)
# This always comes last, so we can consume the rest of the lines.
grok_build_id "$1"
;;
esac
done
return $status
}
grok_build_id() {
# The first line looks like:
# 0x000001c8 04000000 08000000 03000000 474e5500 ............GNU.
# where 0x000001c8 is the address of the section. Subsequent lines look
# similar, but possibly with an odd ending:
# 0x000001d8 9470536b 2dd1e5e7 .pSk-...
local -a hex words=()
read -a hex
local addr="${hex[0]}"
local hexp='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
while true; do
local n=${#hex[@]} i=1
# The "...text..." portion can contain spaces, so the word count
# is not a reliable way to detect the last hex word. So also stop
# when the first non-hex word is seen.
while ((i < n - 1)) && [[ "${hex[$i]}" == $hexp ]]; do
words+=("${hex[$i]}")
((i++))
done
read -a hex || break
done
local -a bytes=()
local w
for w in "${words[@]}"; do
bytes+=("0x${w:0:2}," "0x${w:2:2}," "0x${w:4:2}," "0x${w:6:2},")
done
echo "#define ${1}_BUILD_ID_NOTE_ADDRESS $addr" >> "$OUTFILE"
echo "#define ${1}_BUILD_ID_NOTE_SIZE ${#bytes[@]}" >> "$OUTFILE"
echo "#define ${1}_BUILD_ID_NOTE_BYTES {${bytes[@]}}" >> "$OUTFILE"
}
find_segments() {
# This if silliness disarms -e so we can observe grok_segments's return code.
if {
"$READELF" -W -S -l -x.note.gnu.build-id "$2" | grok_segments "$1"
case $? in
$SEGMENTS_NO_DYNSYM) have_dynsym=no ;;
$SEGMENTS_HAVE_DYNSYM) have_dynsym=yes ;;
*) exit $? ;;
esac
return 0
}; then : ; fi
}
trap 'rm -f "$OUTFILE" ${DEPFILE:+"$DEPFILE"}' ERR HUP INT TERM
while [ $# -gt 0 ]; do
if [ "$1" = --writable ]; then
WRITABLE=true
shift
else
WRITABLE=false
fi
if [ $# -lt 2 ]; then
usage
fi
name="$1"
dso="$2"
shift 2
# This argument might be a response file.
if [[ "$dso" == @* ]]; then
rspfile="${dso#@}"
dso="$(< "$rspfile")"
USED_FILES+=("$rspfile" "$dso")
else
USED_FILES+=("$dso")
fi
echo "#define ${name}_FILENAME \"${dso}\"" > $OUTFILE
find_segments "$name" "$dso"
find_code_symbols "$name" "$dso"
if [ $have_dynsym = yes ]; then
find_dynsym_slots "$name" "$dso"
fi
done
if [ -n "$DEPFILE" ]; then
echo "$OUTFILE: ${USED_FILES[*]}" > "$DEPFILE"
fi