1#!/usr/bin/env bash
2
3# Copyright 2016 The Fuchsia Authors
4#
5# Use of this source code is governed by a MIT-style
6# license that can be found in the LICENSE file or at
7# https://opensource.org/licenses/MIT
8
9# This script reads symbols with nm and writes a C header file that
10# defines macros <NAME>_CODE_*, <NAME>_DATA_* and <NAME>_ENTRY, with
11# the address constants found in the symbol table for the symbols
12# CODE_*, DATA_* and _start, respectively.
13#
14# When there is a dynamic symbol table, then it also emits macros
15# <NAME>_DYNSYM_* giving the dynamic symbol table index of each
16# exported symbol, and <NAME>_DYNSYM_COUNT giving the total number
17# of entries in the table.
18
19usage() {
20  echo >&2 "Usage: $0 NM READELF OUTFILE {NAME DSO}..."
21  exit 2
22}
23
24if [ $# -lt 3 ]; then
25  usage
26fi
27
28NM="$1"
29shift
30READELF="$1"
31shift
32OUTFILE="$1"
33shift
34
35set -e
36if [ -n "$BASH_VERSION" ]; then
37  set -o pipefail
38fi
39
40grok_code_symbols() {
41  local symbol type addr size rest
42  while read symbol type addr size rest; do
43    case "$symbol" in
44    CODE_*|DATA_*|SYSCALL_*|_start)
45      if [ "$symbol" = _start ]; then
46        symbol=ENTRY
47      fi
48      echo "#define ${1}_${symbol} 0x${addr}" >> $OUTFILE
49      case "$size" in
50      ''|0|0x0) ;;
51      *) echo "#define ${1}_${symbol}_SIZE 0x${size}" >> $OUTFILE
52      esac
53      status=0
54      ;;
55    esac
56  done
57  return $status
58}
59
60find_code_symbols() {
61  "$NM" -P -S -n "$2" | grok_code_symbols "$1"
62}
63
64grok_dynsym_slots() {
65  local symno=0
66  local symbol rest
67  while read symbol rest; do
68    symno=$((symno+1))
69    echo "#define ${1}_DYNSYM_${symbol} ${symno}" >> $OUTFILE
70  done
71  if [ $symno -gt 0 ]; then
72    symno=$((symno+1))
73    echo "#define ${1}_DYNSYM_COUNT ${symno}" >> $OUTFILE
74  fi
75}
76
77find_dynsym_slots() {
78  "$NM" -P -D -p "$2" | grok_dynsym_slots "$1"
79}
80
81SEGMENTS_HAVE_DYNSYM=22
82SEGMENTS_NO_DYNSYM=33
83
84grok_segments() {
85  local line
86  local status=$SEGMENTS_NO_DYNSYM
87  while read line; do
88    case "$line" in
89    # Program header for the code segment, e.g.:
90    # LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x00f50f 0x00f50f R E 0x1000
91    #                         ^^^^^ vaddr ^^^^^^                    ^filesz^
92    *LOAD*\ R\ E\ *)
93      local words=($line)
94      local vaddr=$(printf '%#x' ${words[2]})
95      local filesz=$(printf '%#x' ${words[4]})
96      echo "#define ${1}_CODE_START $vaddr" >> $OUTFILE
97      echo "#define ${1}_CODE_END (((${1}_CODE_START + $filesz + (1 << PAGE_SIZE_SHIFT) - 1) >> PAGE_SIZE_SHIFT) << PAGE_SIZE_SHIFT)" >> $OUTFILE
98      ;;
99    # Make sure there's no writable segment.
100    *LOAD*W*)
101      echo >&2 "$0: writable segment: $line"
102      exit 1
103      ;;
104    # Section header for .dynsym, e.g.:
105    #  [ 3] .dynsym           DYNSYM          0000000000001268 001268 000018 18   A  6   1  8
106    #                                         ^^^^^ addr ^^^^^        ^size^ ^entsize^
107    *DYNSYM*)
108      line="${line#*DYNSYM}"
109      local words=($line)
110      local addr=$(printf '%#x' 0x${words[0]})
111      local size=$(printf '%#x' 0x${words[2]})
112      local entsize=$(printf '%#x' 0x${words[3]})
113      # A dummy .dynsym has a single entry.  Don't count that case.
114      if [ $size != $entsize ]; then
115        status=$SEGMENTS_HAVE_DYNSYM
116        echo "#define ${1}_DATA_START_dynsym $addr" >> $OUTFILE
117        echo "#define ${1}_DATA_END_dynsym (${1}_DATA_START_dynsym + $size)" >> $OUTFILE
118      fi
119      ;;
120    esac
121  done
122  return $status
123}
124
125find_segments() {
126  # This if silliness disarms -e so we can observe grok_segments's return code.
127  if {
128  "$READELF" -W -S -l "$2" | grok_segments "$1"
129  case $? in
130  $SEGMENTS_NO_DYNSYM) have_dynsym=no ;;
131  $SEGMENTS_HAVE_DYNSYM) have_dynsym=yes ;;
132  *) exit $? ;;
133  esac
134  return 0
135  }; then : ; fi
136}
137
138while [ $# -gt 0 ]; do
139  if [ $# -lt 2 ]; then
140    usage
141  fi
142  echo "#define ${1}_FILENAME \"${2}\"" > $OUTFILE
143  find_segments "$1" "$2"
144  find_code_symbols "$1" "$2"
145  if [ $have_dynsym = yes ]; then
146    find_dynsym_slots "$1" "$2"
147  fi
148  shift 2
149done
150